From 1c1afa1c962b99fe082d8f7e1dfc06336922ec7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 19:16:20 +0900 Subject: [PATCH 001/137] Move MaxCombo to base DifficultyAttributes --- osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 1 - osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs | 1 - osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 1 + 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 75f5b18607..fa9011d826 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty public class CatchDifficultyAttributes : DifficultyAttributes { public double ApproachRate; - public int MaxCombo; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 6e991a1d08..a9879013f8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -11,6 +11,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double SpeedStrain; public double ApproachRate; public double OverallDifficulty; - public int MaxCombo; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 75d3807bba..00ad956c8f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public class TaikoDifficultyAttributes : DifficultyAttributes { public double GreatHitWindow; - public int MaxCombo; } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index b4b4bb9cd1..732dc772b7 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Difficulty public Skill[] Skills; public double StarRating; + public int MaxCombo; public DifficultyAttributes() { From 85bda29b71a790cb7675d46b86dce2462deae3c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 19:16:24 +0900 Subject: [PATCH 002/137] Add mania max combo attribute --- osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 37cba1fd3c..b08c520c54 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mania.Difficulty.Skills; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -43,6 +44,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, + MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), Skills = skills }; } From 4d15f0fe520f188c9219aa7e716679967d4c5f49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 19:16:46 +0900 Subject: [PATCH 003/137] Implement basic score recalculation --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 10 ++++--- .../Online/Leaderboards/LeaderboardScore.cs | 4 +-- osu.Game/OsuGameBase.cs | 9 +++--- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 5 +++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 +++++--- osu.Game/Scoring/ScoreManager.cs | 30 ++++++++++++++++++- 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index b80b4e45ed..c02b6002d9 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -201,11 +201,11 @@ namespace osu.Game.Beatmaps var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo)); var attributes = calculator.Calculate(key.Mods); - return difficultyCache[key] = new StarDifficulty(attributes.StarRating); + return difficultyCache[key] = new StarDifficulty(attributes.StarRating, attributes.MaxCombo); } catch { - return difficultyCache[key] = new StarDifficulty(0); + return difficultyCache[key] = new StarDifficulty(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps if (beatmapInfo.ID == 0 || rulesetInfo.ID == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). - existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty); + existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty, beatmapInfo.MaxCombo ?? 0); key = default; return true; @@ -292,10 +292,12 @@ namespace osu.Game.Beatmaps public readonly struct StarDifficulty { public readonly double Stars; + public readonly int MaxCombo; - public StarDifficulty(double stars) + public StarDifficulty(double stars, int maxCombo) { Stars = stars; + MaxCombo = maxCombo; // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 87b283f6b5..3c7ef73594 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -72,7 +72,7 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load(IAPIProvider api, OsuColour colour) + private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager) { var user = score.User; @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Text = score.TotalScore.ToString(@"N0"), + Text = scoreManager.GetTotalScore(score).ToString(@"N0"), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 98f60d52d3..1d92315b1f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -57,6 +57,8 @@ namespace osu.Game protected ScoreManager ScoreManager; + protected BeatmapDifficultyManager DifficultyManager; + protected SkinManager SkinManager; protected RulesetStore RulesetStore; @@ -194,7 +196,7 @@ namespace osu.Game dependencies.Cache(FileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyManager)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); // this should likely be moved to ArchiveModelManager when another case appers where it is necessary @@ -218,9 +220,8 @@ namespace osu.Game ScoreManager.Undelete(getBeatmapScores(item), true); }); - var difficultyManager = new BeatmapDifficultyManager(); - dependencies.Cache(difficultyManager); - AddInternal(difficultyManager); + dependencies.Cache(DifficultyManager = new BeatmapDifficultyManager()); + AddInternal(DifficultyManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 097ca27bf7..a3500826a0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -25,6 +25,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const float row_height = 22; private const int text_size = 12; + [Resolved] + private ScoreManager scoreManager { get; set; } + private readonly FillFlowContainer backgroundFlow; private Color4 highAccuracyColour; @@ -121,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Text = $@"{score.TotalScore:N0}", + Text = $@"{scoreManager.GetTotalScore(score):N0}", Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index eac47aa089..057b1820f7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -203,19 +203,24 @@ namespace osu.Game.Rulesets.Scoring } private double getScore(ScoringMode mode) + { + return GetScore(baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore, mode); + } + + public double GetScore(double accuracyRatio, double comboRatio, double bonus, ScoringMode mode) { switch (mode) { default: case ScoringMode.Standardised: - double accuracyScore = accuracyPortion * baseScore / maxBaseScore; - double comboScore = comboPortion * HighestCombo.Value / maxHighestCombo; + double accuracyScore = accuracyPortion * accuracyRatio; + double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + bonus) * scoreMultiplier; case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier / 25); + return bonus + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d5bd486e43..a82970d3df 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; @@ -15,6 +16,7 @@ using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring.Legacy; namespace osu.Game.Scoring @@ -30,11 +32,16 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + [CanBeNull] + private readonly Func difficulties; + + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, + Func difficulties = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; this.beatmaps = beatmaps; + this.difficulties = difficulties; } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -72,5 +79,26 @@ namespace osu.Game.Scoring protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); + + public long GetTotalScore(ScoreInfo score) + { + int? beatmapMaxCombo = score.Beatmap.MaxCombo; + + if (beatmapMaxCombo == null) + { + if (score.Beatmap.ID == 0 || difficulties == null) + return score.TotalScore; // Can't do anything. + + // We can compute the max combo locally. + beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + } + + var ruleset = score.Ruleset.CreateInstance(); + var scoreProcessor = ruleset.CreateScoreProcessor(); + + scoreProcessor.Mods.Value = score.Mods; + + return (long)Math.Round(scoreProcessor.GetScore(score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo.Value, 0, ScoringMode.Standardised)); + } } } From 1e5e5cae0cbeee115aa09337051e22c943164893 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 21:34:34 +0900 Subject: [PATCH 004/137] Add support for standardised -> classic changes --- .../Graphics/Sprites/GlowingSpriteText.cs | 7 +++ .../Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +-- osu.Game/Scoring/ScoreManager.cs | 60 +++++++++++++++---- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 4aea5aa518..85df2d167f 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -1,6 +1,7 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -55,6 +56,12 @@ namespace osu.Game.Graphics.Sprites set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value; } + public Bindable Current + { + get => spriteText.Current; + set => spriteText.Current = value; + } + public GlowingSpriteText() { AutoSizeAxes = Axes.Both; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 3c7ef73594..24bb43f1b7 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Text = scoreManager.GetTotalScore(score).ToString(@"N0"), + Current = scoreManager.GetTotalScore(score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1d92315b1f..3839d4e734 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -196,7 +196,7 @@ namespace osu.Game dependencies.Cache(FileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyManager)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyManager, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); // this should likely be moved to ArchiveModelManager when another case appers where it is necessary diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a3500826a0..832ac75882 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Text = $@"{scoreManager.GetTotalScore(score):N0}", + Current = scoreManager.GetTotalScore(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 057b1820f7..7d138bd878 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -204,10 +204,10 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore, mode); + return GetScore(mode, maxBaseScore, maxHighestCombo, baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore); } - public double GetScore(double accuracyRatio, double comboRatio, double bonus, ScoringMode mode) + public double GetScore(ScoringMode mode, double maxBaseScore, double maxHighestCombo, double accuracyRatio, double comboRatio, double bonusScore) { switch (mode) { @@ -216,11 +216,11 @@ namespace osu.Game.Rulesets.Scoring double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + bonus) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return bonus + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); + return bonusScore + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index a82970d3df..134a41a7d4 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -8,9 +8,11 @@ using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Online.API; @@ -35,13 +37,17 @@ namespace osu.Game.Scoring [CanBeNull] private readonly Func difficulties; + [CanBeNull] + private readonly OsuConfigManager configManager; + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, - Func difficulties = null) + Func difficulties = null, OsuConfigManager configManager = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; this.beatmaps = beatmaps; this.difficulties = difficulties; + this.configManager = configManager; } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -80,25 +86,55 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public long GetTotalScore(ScoreInfo score) + public Bindable GetTotalScore(ScoreInfo score) { - int? beatmapMaxCombo = score.Beatmap.MaxCombo; + var bindable = new TotalScoreBindable(score, difficulties); + configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); + return bindable; + } - if (beatmapMaxCombo == null) + private class TotalScoreBindable : Bindable + { + public readonly Bindable ScoringMode = new Bindable(); + + private readonly ScoreInfo score; + private readonly Func difficulties; + + public TotalScoreBindable(ScoreInfo score, Func difficulties) { - if (score.Beatmap.ID == 0 || difficulties == null) - return score.TotalScore; // Can't do anything. + this.score = score; + this.difficulties = difficulties; - // We can compute the max combo locally. - beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + ScoringMode.BindValueChanged(onScoringModeChanged, true); } - var ruleset = score.Ruleset.CreateInstance(); - var scoreProcessor = ruleset.CreateScoreProcessor(); + private void onScoringModeChanged(ValueChangedEvent mode) + { + int? beatmapMaxCombo = score.Beatmap.MaxCombo; - scoreProcessor.Mods.Value = score.Mods; + if (beatmapMaxCombo == null) + { + if (score.Beatmap.ID == 0 || difficulties == null) + { + // We don't have enough information (max combo) to compute the score, so let's use the provided score. + Value = score.TotalScore.ToString("N0"); + return; + } - return (long)Math.Round(scoreProcessor.GetScore(score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo.Value, 0, ScoringMode.Standardised)); + // We can compute the max combo locally. + beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + } + + var ruleset = score.Ruleset.CreateInstance(); + var scoreProcessor = ruleset.CreateScoreProcessor(); + + scoreProcessor.Mods.Value = score.Mods; + + double maxBaseScore = 300 * beatmapMaxCombo.Value; + double maxHighestCombo = beatmapMaxCombo.Value; + + Value = Math.Round(scoreProcessor.GetScore(mode.NewValue, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); + } } } } From 39f8b5eb854df85ff989c71e57aad50dfb558bbf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 21:45:27 +0900 Subject: [PATCH 005/137] Use async difficulty calculation --- osu.Game/Scoring/ScoreManager.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 134a41a7d4..8d3872cda0 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -108,6 +108,8 @@ namespace osu.Game.Scoring ScoringMode.BindValueChanged(onScoringModeChanged, true); } + private IBindable difficultyBindable; + private void onScoringModeChanged(ValueChangedEvent mode) { int? beatmapMaxCombo = score.Beatmap.MaxCombo; @@ -121,19 +123,25 @@ namespace osu.Game.Scoring return; } - // We can compute the max combo locally. - beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + // We can compute the max combo locally after the async beatmap difficulty computation. + difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods); + difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); } + else + updateScore(beatmapMaxCombo.Value); + } + private void updateScore(int beatmapMaxCombo) + { var ruleset = score.Ruleset.CreateInstance(); var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - double maxBaseScore = 300 * beatmapMaxCombo.Value; - double maxHighestCombo = beatmapMaxCombo.Value; + double maxBaseScore = 300 * beatmapMaxCombo; + double maxHighestCombo = beatmapMaxCombo; - Value = Math.Round(scoreProcessor.GetScore(mode.NewValue, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); + Value = Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); } } } From 8ffc4309fb14937c57dfad9270968d3488cbc91c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:23:44 +0900 Subject: [PATCH 006/137] Fix possible NaN values --- osu.Game/Scoring/ScoreManager.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8d3872cda0..0165c5dc82 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -105,6 +105,8 @@ namespace osu.Game.Scoring this.score = score; this.difficulties = difficulties; + Value = "0"; + ScoringMode.BindValueChanged(onScoringModeChanged, true); } @@ -133,6 +135,12 @@ namespace osu.Game.Scoring private void updateScore(int beatmapMaxCombo) { + if (beatmapMaxCombo == 0) + { + Value = "0"; + return; + } + var ruleset = score.Ruleset.CreateInstance(); var scoreProcessor = ruleset.CreateScoreProcessor(); From d7bbb362bf5f0051bbecd4468e63be079a3252f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:51:19 +0900 Subject: [PATCH 007/137] Separate bindables --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 26 ++++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 24bb43f1b7..846bebe347 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetTotalScore(score), + Current = scoreManager.GetTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 832ac75882..6bebd98eef 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Current = scoreManager.GetTotalScore(score), + Current = scoreManager.GetTotalScoreString(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 0165c5dc82..1943cab992 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -86,14 +86,16 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public Bindable GetTotalScore(ScoreInfo score) + public Bindable GetTotalScore(ScoreInfo score) { var bindable = new TotalScoreBindable(score, difficulties); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); return bindable; } - private class TotalScoreBindable : Bindable + public Bindable GetTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetTotalScore(score)); + + private class TotalScoreBindable : Bindable { public readonly Bindable ScoringMode = new Bindable(); @@ -105,7 +107,7 @@ namespace osu.Game.Scoring this.score = score; this.difficulties = difficulties; - Value = "0"; + Value = 0; ScoringMode.BindValueChanged(onScoringModeChanged, true); } @@ -121,7 +123,7 @@ namespace osu.Game.Scoring if (score.Beatmap.ID == 0 || difficulties == null) { // We don't have enough information (max combo) to compute the score, so let's use the provided score. - Value = score.TotalScore.ToString("N0"); + Value = score.TotalScore; return; } @@ -137,7 +139,7 @@ namespace osu.Game.Scoring { if (beatmapMaxCombo == 0) { - Value = "0"; + Value = 0; return; } @@ -149,7 +151,19 @@ namespace osu.Game.Scoring double maxBaseScore = 300 * beatmapMaxCombo; double maxHighestCombo = beatmapMaxCombo; - Value = Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); + Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)); + } + } + + private class TotalScoreStringBindable : Bindable + { + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (need to hold a reference) + private readonly IBindable totalScore; + + public TotalScoreStringBindable(IBindable totalScore) + { + this.totalScore = totalScore; + this.totalScore.BindValueChanged(v => Value = v.NewValue.ToString("N0"), true); } } } From ec2674e1ea60be3b7a649b38da3ff5ce39eadbe0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:51:39 +0900 Subject: [PATCH 008/137] Fix nullref with null beatmap --- osu.Game/Scoring/ScoreManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 1943cab992..634cca159a 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -116,6 +116,12 @@ namespace osu.Game.Scoring private void onScoringModeChanged(ValueChangedEvent mode) { + if (score.Beatmap == null) + { + Value = score.TotalScore; + return; + } + int? beatmapMaxCombo = score.Beatmap.MaxCombo; if (beatmapMaxCombo == null) From c1838902a669f3e8fb7c4b0c1e25bf50dfa36c84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:51:48 +0900 Subject: [PATCH 009/137] Add to more places --- .../Graphics/UserInterface/RollingCounter.cs | 14 ++++++---- .../Scores/TopScoreStatisticsSection.cs | 28 ++++++++++++++++++- .../ContractedPanelMiddleContent.cs | 5 +++- .../Expanded/ExpandedPanelMiddleContent.cs | 9 +++--- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 6763198213..a469927595 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -9,16 +9,20 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { - public abstract class RollingCounter : Container + public abstract class RollingCounter : Container, IHasCurrentValue where T : struct, IEquatable { - /// - /// The current value. - /// - public Bindable Current = new Bindable(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } private SpriteText displayedCountSpriteText; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index a92346e0fe..507c692eb1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -38,6 +39,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer statisticsColumns; private readonly ModsInfoColumn modsColumn; + [Resolved] + private ScoreManager scoreManager { get; set; } + public TopScoreStatisticsSection() { RelativeSizeAxes = Axes.X; @@ -87,6 +91,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; } + [BackgroundDependencyLoader] + private void load() + { + if (score != null) + totalScoreColumn.Current = scoreManager.GetTotalScoreString(score); + } + + private ScoreInfo score; + /// /// Sets the score to be displayed. /// @@ -94,7 +107,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { - totalScoreColumn.Text = $@"{value.TotalScore:N0}"; + if (score == value) + return; + + score = value; + accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; @@ -102,6 +119,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); modsColumn.Mods = value.Mods; + + if (IsLoaded) + totalScoreColumn.Current = scoreManager.GetTotalScoreString(value); } } @@ -190,6 +210,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set => text.Text = value; } + + public Bindable Current + { + get => text.Current; + set => text.Current = value; + } } private class ModsInfoColumn : InfoColumn diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 8cd0e7025e..b37b89e6c0 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Contracted { private readonly ScoreInfo score; + [Resolved] + private ScoreManager scoreManager { get; set; } + /// /// Creates a new . /// @@ -160,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = score.TotalScore.ToString("N0"), + Current = scoreManager.GetTotalScoreString(score), Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), Spacing = new Vector2(-1, 0) }, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 01502c0913..3433410d3c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -25,15 +25,16 @@ namespace osu.Game.Screens.Ranking.Expanded /// public class ExpandedPanelMiddleContent : CompositeDrawable { - private readonly ScoreInfo score; + private const float padding = 10; + private readonly ScoreInfo score; private readonly List statisticDisplays = new List(); private FillFlowContainer starAndModDisplay; - private RollingCounter scoreCounter; - private const float padding = 10; + [Resolved] + private ScoreManager scoreManager { get; set; } /// /// Creates a new . @@ -238,7 +239,7 @@ namespace osu.Game.Screens.Ranking.Expanded using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true)) { scoreCounter.FadeIn(); - scoreCounter.Current.Value = score.TotalScore; + scoreCounter.Current = scoreManager.GetTotalScore(score); double delay = 0; From e4cb7eb964b6b5b7e1072c0a1efa9d613351da59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Sep 2020 17:28:41 +0900 Subject: [PATCH 010/137] Initial structure --- osu.Game/Collections/CollectionManager.cs | 93 +++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 osu.Game/Collections/CollectionManager.cs diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs new file mode 100644 index 0000000000..1058e7b5b8 --- /dev/null +++ b/osu.Game/Collections/CollectionManager.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.IO.Legacy; + +namespace osu.Game.Collections +{ + public class CollectionManager + { + private const string import_from_stable_path = "collection.db"; + + private readonly BeatmapManager beatmaps; + + public CollectionManager(BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + } + + /// + /// Set a storage with access to an osu-stable install for import purposes. + /// + public Func GetStableStorage { private get; set; } + + /// + /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. + /// + public Task ImportFromStableAsync() + { + var stable = GetStableStorage?.Invoke(); + + if (stable == null) + { + Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + if (!stable.ExistsDirectory(import_from_stable_path)) + { + // This handles situations like when the user does not have a Skins folder + Logger.Log($"No {import_from_stable_path} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); + } + + private List readCollection(Stream stream) + { + var result = new List(); + + using (var reader = new SerializationReader(stream)) + { + reader.ReadInt32(); // Version + + int collectionCount = reader.ReadInt32(); + result.Capacity = collectionCount; + + for (int i = 0; i < collectionCount; i++) + { + var collection = new BeatmapCollection { Name = reader.ReadString() }; + + int mapCount = reader.ReadInt32(); + collection.Beatmaps.Capacity = mapCount; + + for (int j = 0; j < mapCount; j++) + { + string checksum = reader.ReadString(); + + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); + if (beatmap != null) + collection.Beatmaps.Add(beatmap); + } + } + } + + return result; + } + } + + public class BeatmapCollection + { + public string Name; + + public readonly List Beatmaps = new List(); + } +} From 78648cb90d409878171b9b9cc500a4d9adae28cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Sep 2020 19:33:06 +0900 Subject: [PATCH 011/137] Add reading from local file --- osu.Game/Collections/CollectionManager.cs | 65 +++++++++++++---------- osu.Game/OsuGameBase.cs | 7 +++ 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 1058e7b5b8..302d892efb 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,23 +4,33 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; -using osu.Framework.Logging; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; namespace osu.Game.Collections { - public class CollectionManager + public class CollectionManager : CompositeDrawable { - private const string import_from_stable_path = "collection.db"; + private const string database_name = "collection.db"; - private readonly BeatmapManager beatmaps; + public IBindableList Collections => collections; + private readonly BindableList collections = new BindableList(); - public CollectionManager(BeatmapManager beatmaps) + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [BackgroundDependencyLoader] + private void load(GameHost host) { - this.beatmaps = beatmaps; + if (host.Storage.Exists(database_name)) + { + using (var stream = host.Storage.GetStream(database_name)) + collections.AddRange(readCollection(stream)); + } } /// @@ -31,26 +41,25 @@ namespace osu.Game.Collections /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public Task ImportFromStableAsync() - { - var stable = GetStableStorage?.Invoke(); - - if (stable == null) - { - Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - - if (!stable.ExistsDirectory(import_from_stable_path)) - { - // This handles situations like when the user does not have a Skins folder - Logger.Log($"No {import_from_stable_path} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - - return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); - } - + // public Task ImportFromStableAsync() + // { + // var stable = GetStableStorage?.Invoke(); + // + // if (stable == null) + // { + // Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); + // return Task.CompletedTask; + // } + // + // if (!stable.ExistsDirectory(database_name)) + // { + // // This handles situations like when the user does not have a Skins folder + // Logger.Log($"No {database_name} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + // return Task.CompletedTask; + // } + // + // return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); + // } private List readCollection(Stream stream) { var result = new List(); @@ -77,6 +86,8 @@ namespace osu.Game.Collections if (beatmap != null) collection.Beatmaps.Add(beatmap); } + + result.Add(collection); } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 98f60d52d3..3ba164e87f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -26,6 +26,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -55,6 +56,8 @@ namespace osu.Game protected BeatmapManager BeatmapManager; + protected CollectionManager CollectionManager; + protected ScoreManager ScoreManager; protected SkinManager SkinManager; @@ -222,6 +225,10 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); + var collectionManager = new CollectionManager(); + dependencies.Cache(collectionManager); + AddInternal(collectionManager); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); From 4459287b35f1a85461661f5d98042dbb2334b066 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:25:12 +0900 Subject: [PATCH 012/137] Add filter control test scene --- .../SongSelect/TestSceneFilterControl.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs new file mode 100644 index 0000000000..f89300661c --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -0,0 +1,22 @@ +// 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.Graphics; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneFilterControl : OsuTestScene + { + public TestSceneFilterControl() + { + Child = new FilterControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = FilterControl.HEIGHT, + }; + } + } +} From bb090a55e03b35c47192ef9c6f8bce46a84c74e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:25:25 +0900 Subject: [PATCH 013/137] Add dropdown to filter control --- osu.Game/Screens/Select/FilterControl.cs | 196 +++++++++++++++++------ 1 file changed, 150 insertions(+), 46 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e111ec4b15..24ea4946af 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,12 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -21,7 +26,8 @@ namespace osu.Game.Screens.Select { public class FilterControl : Container { - public const float HEIGHT = 100; + public const float HEIGHT = 2 * side_margin + 85; + private const float side_margin = 20; public Action FilterChanged; @@ -41,6 +47,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, + Collection = collectionDropdown?.Current.Value }; if (!minimumStars.IsDefault) @@ -54,6 +61,7 @@ namespace osu.Game.Screens.Select } private SeekLimitedSearchTextBox searchTextBox; + private CollectionFilterDropdown collectionDropdown; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); @@ -90,73 +98,169 @@ namespace osu.Game.Screens.Select }, new Container { - Padding = new MarginPadding(20), + Padding = new MarginPadding(side_margin), RelativeSizeAxes = Axes.Both, Width = 0.5f, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Children = new Drawable[] + Child = new GridContainer { - searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, - new Box + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(80), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + new Dimension(GridSizeMode.Absolute, 60), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Absolute, 20), }, - new FillFlowContainer + Content = new[] { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), - Children = new Drawable[] + new Drawable[] { - new OsuTabControlCheckbox + new Container { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - sortTabs = new OsuTabControl + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = OsuColour.Gray(80), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), + Children = new Drawable[] + { + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + sortTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + new OsuSpriteText + { + Text = "Sort by", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, + } + } + }, + null, + new Drawable[] + { + new Container { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - new OsuSpriteText - { - Text = "Sort by", - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - } - }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + collectionDropdown = new CollectionFilterDropdown + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } + } + } + }, + } } } }; - searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria()); + collectionDropdown.Current.ValueChanged += _ => updateCriteria(); + searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); } + private class CollectionFilterDropdown : OsuDropdown + { + private readonly IBindableList collections = new BindableList(); + private readonly BindableList filters = new BindableList(); + + public CollectionFilterDropdown() + { + ItemSource = filters; + } + + [BackgroundDependencyLoader] + private void load(CollectionManager collectionManager) + { + collections.BindTo(collectionManager.Collections); + collections.CollectionChanged += (_, __) => updateItems(); + updateItems(); + } + + private void updateItems() + { + var selectedItem = SelectedItem?.Value?.Collection; + + filters.Clear(); + filters.Add(new CollectionFilter(null)); + filters.AddRange(collections.Select(c => new CollectionFilter(c))); + + Current.Value = filters.FirstOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + } + + protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name ?? "All beatmaps"; + + protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + + private class CollectionDropdownHeader : OsuDropdownHeader + { + public CollectionDropdownHeader() + { + Height = 25; + Icon.Size = new Vector2(16); + Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; + } + } + } + + public class CollectionFilter + { + [CanBeNull] + public readonly BeatmapCollection Collection; + + public CollectionFilter([CanBeNull] BeatmapCollection collection) + { + Collection = collection; + } + + public virtual bool ContainsBeatmap(BeatmapInfo beatmap) + => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; + } + public void Deactivate() { searchTextBox.ReadOnly = true; - searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) GetContainingInputManager().ChangeFocus(searchTextBox); From 9dde37fe40b7a49e5743844de3bb42007d49ea9e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:25:36 +0900 Subject: [PATCH 014/137] Hook up collection filter --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 3 +++ osu.Game/Screens/Select/FilterCriteria.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index ed54c158db..8e5655e514 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -60,6 +60,9 @@ namespace osu.Game.Screens.Select.Carousel match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); } + if (match) + match &= criteria.Collection?.ContainsBeatmap(Beatmap) ?? true; + Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 18be4fcac8..5a5c0e1b50 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Select } } + public FilterControl.CollectionFilter Collection; + public struct OptionalRange : IEquatable> where T : struct { From 6d5e155106d721940bb6a9f56822511986b86853 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:44:26 +0900 Subject: [PATCH 015/137] Change to BindableList to notify of changes --- osu.Game/Collections/CollectionManager.cs | 4 +-- osu.Game/Screens/Select/FilterControl.cs | 41 ++++++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 302d892efb..3ba604cc39 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -74,9 +74,7 @@ namespace osu.Game.Collections for (int i = 0; i < collectionCount; i++) { var collection = new BeatmapCollection { Name = reader.ReadString() }; - int mapCount = reader.ReadInt32(); - collection.Beatmaps.Capacity = mapCount; for (int j = 0; j < mapCount; j++) { @@ -99,6 +97,6 @@ namespace osu.Game.Collections { public string Name; - public readonly List Beatmaps = new List(); + public readonly BindableList Beatmaps = new BindableList(); } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 24ea4946af..58c27751fd 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -214,11 +215,21 @@ namespace osu.Game.Screens.Select private void load(CollectionManager collectionManager) { collections.BindTo(collectionManager.Collections); - collections.CollectionChanged += (_, __) => updateItems(); - updateItems(); + collections.CollectionChanged += (_, __) => collectionsChanged(); + collectionsChanged(); } - private void updateItems() + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(filterChanged); + } + + /// + /// Occurs when a collection has been added or removed. + /// + private void collectionsChanged() { var selectedItem = SelectedItem?.Value?.Collection; @@ -226,7 +237,29 @@ namespace osu.Game.Screens.Select filters.Add(new CollectionFilter(null)); filters.AddRange(collections.Select(c => new CollectionFilter(c))); - Current.Value = filters.FirstOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + } + + /// + /// Occurs when the selection has changed. + /// + private void filterChanged(ValueChangedEvent filter) + { + if (filter.OldValue?.Collection != null) + filter.OldValue.Collection.Beatmaps.CollectionChanged -= filterBeatmapsChanged; + + if (filter.NewValue?.Collection != null) + filter.NewValue.Collection.Beatmaps.CollectionChanged += filterBeatmapsChanged; + } + + /// + /// Occurs when the beatmaps contained by a have changed. + /// + private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. + // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. + Current.TriggerChange(); } protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name ?? "All beatmaps"; From 094ddecc9510f5e5e020c24b5952f979ad673741 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:08:31 +0900 Subject: [PATCH 016/137] Add dropdowns to carousel items --- .../Carousel/DrawableCarouselBeatmap.cs | 26 ++++++++++ .../Carousel/DrawableCarouselBeatmapSet.cs | 48 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index c559b4f8f5..760b888288 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,6 +18,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; @@ -46,6 +48,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapDifficultyManager difficultyManager { get; set; } + [Resolved] + private CollectionManager collectionManager { get; set; } + private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -219,10 +224,31 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + items.Add(new OsuMenuItem("Add to...") + { + Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) + .ToArray() + }); + return items.ToArray(); } } + private MenuItem createCollectionMenuItem(BeatmapCollection collection) + { + return new ToggleMenuItem(collection.Name, MenuItemType.Standard, s => + { + if (s) + collection.Beatmaps.Add(beatmap); + else + collection.Beatmaps.Remove(beatmap); + }) + { + State = { Value = collection.Beatmaps.Contains(beatmap) } + }; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 5acb6d1946..66851657bc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -34,6 +35,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved] + private CollectionManager collectionManager { get; set; } + private readonly BeatmapSetInfo beatmapSet; public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) @@ -141,10 +145,54 @@ namespace osu.Game.Screens.Select.Carousel if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); + items.Add(new OsuMenuItem("Add all to...") + { + Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) + .ToArray() + }); + return items.ToArray(); } } + private MenuItem createCollectionMenuItem(BeatmapCollection collection) + { + TernaryState state; + + var countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b)); + + if (countExisting == beatmapSet.Beatmaps.Count) + state = TernaryState.True; + else if (countExisting > 0) + state = TernaryState.Indeterminate; + else + state = TernaryState.False; + + return new TernaryStateMenuItem(collection.Name, MenuItemType.Standard, s => + { + foreach (var b in beatmapSet.Beatmaps) + { + switch (s) + { + case TernaryState.True: + if (collection.Beatmaps.Contains(b)) + continue; + + collection.Beatmaps.Add(b); + break; + + case TernaryState.False: + collection.Beatmaps.Remove(b); + break; + } + } + }) + { + State = { Value = state } + }; + } + private class PanelBackground : BufferedContainer { public PanelBackground(WorkingBeatmap working) From d363a5d164755423b23c5bd1c3531605d0866f2b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:19:15 +0900 Subject: [PATCH 017/137] Add basic ordering --- osu.Game/Collections/CollectionManager.cs | 9 +++++++++ .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 3ba604cc39..c18cc30427 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -98,5 +98,14 @@ namespace osu.Game.Collections public string Name; public readonly BindableList Beatmaps = new BindableList(); + + public DateTimeOffset LastModifyTime { get; private set; } + + public BeatmapCollection() + { + LastModifyTime = DateTimeOffset.UtcNow; + + Beatmaps.CollectionChanged += (_, __) => LastModifyTime = DateTimeOffset.Now; + } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 760b888288..1bd4447248 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Add to...") { - Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) .ToArray() }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 66851657bc..e05b5ee951 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Add all to...") { - Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) .ToArray() }); From 5ebead2bfd27a6df4c7150625f9d9e0da38b1116 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:44:07 +0900 Subject: [PATCH 018/137] Prevent ValueChanged binds to external bindable --- osu.Game/Screens/Select/FilterControl.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 58c27751fd..d49d0c57e6 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -204,6 +204,7 @@ namespace osu.Game.Screens.Select private class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); + private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); public CollectionFilterDropdown() @@ -223,7 +224,8 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - Current.BindValueChanged(filterChanged); + beatmaps.CollectionChanged += filterBeatmapsChanged; + Current.BindValueChanged(filterChanged, true); } /// @@ -246,10 +248,10 @@ namespace osu.Game.Screens.Select private void filterChanged(ValueChangedEvent filter) { if (filter.OldValue?.Collection != null) - filter.OldValue.Collection.Beatmaps.CollectionChanged -= filterBeatmapsChanged; + beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); if (filter.NewValue?.Collection != null) - filter.NewValue.Collection.Beatmaps.CollectionChanged += filterBeatmapsChanged; + beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); } /// From 02a908752f61509ed45df94d6db41712647f11d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:52:56 +0900 Subject: [PATCH 019/137] Fix stackoverflow --- osu.Game/Screens/Select/FilterControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index d49d0c57e6..fcaacef97b 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -224,7 +224,6 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - beatmaps.CollectionChanged += filterBeatmapsChanged; Current.BindValueChanged(filterChanged, true); } @@ -247,11 +246,15 @@ namespace osu.Game.Screens.Select /// private void filterChanged(ValueChangedEvent filter) { + beatmaps.CollectionChanged -= filterBeatmapsChanged; + if (filter.OldValue?.Collection != null) beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); if (filter.NewValue?.Collection != null) beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); + + beatmaps.CollectionChanged += filterBeatmapsChanged; } /// From 686257167223fec69675cbe5d0b6c2999132c68f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:02:50 +0900 Subject: [PATCH 020/137] Fix IconButton sometimes not recolourising --- osu.Game/Graphics/UserInterface/IconButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index d7e5666545..858f517985 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -24,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface set { iconColour = value; - icon.Colour = value; + icon.FadeColour(value); } } From 661eac8f1dd0731fe0476038be5a3c37b72d3b95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:03:38 +0900 Subject: [PATCH 021/137] Add add/remove button to dropdown items --- osu.Game/Screens/Select/FilterControl.cs | 82 ++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index fcaacef97b..31c3bd6d1c 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -10,12 +11,14 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -271,6 +274,8 @@ namespace osu.Game.Screens.Select protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); + private class CollectionDropdownHeader : OsuDropdownHeader { public CollectionDropdownHeader() @@ -280,6 +285,83 @@ namespace osu.Game.Screens.Select Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; } } + + private class CollectionDropdownMenu : OsuDropdownMenu + { + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); + } + + private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem + { + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + + [CanBeNull] + private readonly BindableList collectionBeatmaps; + + private IconButton addOrRemoveButton; + + public CollectionDropdownMenuItem(MenuItem item) + : base(item) + { + collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + addOrRemoveButton = new IconButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, + Scale = new Vector2(0.75f), + Alpha = collectionBeatmaps == null ? 0 : 1, + Action = addOrRemove + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (collectionBeatmaps != null) + { + collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); + collectionChanged(); + } + } + + private void collectionChanged() + { + Debug.Assert(collectionBeatmaps != null); + + if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) + { + addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.IconColour = colours.Red; + } + else + { + addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.IconColour = colours.Green; + } + } + + private void addOrRemove() + { + Debug.Assert(collectionBeatmaps != null); + + if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) + collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); + } + } } public class CollectionFilter From 7fcbc3a814b24c78f8a2facf901112ec0e0bc561 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:06:17 +0900 Subject: [PATCH 022/137] Respond to changes in beatmap --- osu.Game/Screens/Select/FilterControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 31c3bd6d1c..a8f9835240 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -334,7 +334,7 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps != null) { collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); - collectionChanged(); + beatmap.BindValueChanged(_ => collectionChanged(), true); } } From d83264f5385d2890f4b9ccd4e021ac77498e1d84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:56:13 +0900 Subject: [PATCH 023/137] Add max height --- osu.Game/Screens/Select/FilterControl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a8f9835240..0ae5e70fc2 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -288,6 +288,11 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenu : OsuDropdownMenu { + public CollectionDropdownMenu() + { + MaxHeight = 200; + } + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); } From b7adb4b1fd1c17b0044cb572b329fd7c96f1780f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:31:37 +0900 Subject: [PATCH 024/137] Add background save support + read safety --- osu.Game/Collections/CollectionManager.cs | 130 +++++++++++++++++++--- 1 file changed, 112 insertions(+), 18 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c18cc30427..3f89866749 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,9 +4,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; @@ -15,8 +18,16 @@ namespace osu.Game.Collections { public class CollectionManager : CompositeDrawable { + /// + /// Database version in YYYYMMDD format (matching stable). + /// + private const int database_version = 30000000; + private const string database_name = "collection.db"; + [Resolved] + private GameHost host { get; set; } + public IBindableList Collections => collections; private readonly BindableList collections = new BindableList(); @@ -24,13 +35,17 @@ namespace osu.Game.Collections private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(GameHost host) + private void load() { if (host.Storage.Exists(database_name)) { using (var stream = host.Storage.GetStream(database_name)) collections.AddRange(readCollection(stream)); } + + foreach (var c in collections) + c.Changed += backgroundSave; + collections.CollectionChanged += (_, __) => backgroundSave(); } /// @@ -64,37 +79,112 @@ namespace osu.Game.Collections { var result = new List(); - using (var reader = new SerializationReader(stream)) + try { - reader.ReadInt32(); // Version - - int collectionCount = reader.ReadInt32(); - result.Capacity = collectionCount; - - for (int i = 0; i < collectionCount; i++) + using (var sr = new SerializationReader(stream)) { - var collection = new BeatmapCollection { Name = reader.ReadString() }; - int mapCount = reader.ReadInt32(); + sr.ReadInt32(); // Version - for (int j = 0; j < mapCount; j++) + int collectionCount = sr.ReadInt32(); + result.Capacity = collectionCount; + + for (int i = 0; i < collectionCount; i++) { - string checksum = reader.ReadString(); + var collection = new BeatmapCollection { Name = sr.ReadString() }; + int mapCount = sr.ReadInt32(); - var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); - if (beatmap != null) - collection.Beatmaps.Add(beatmap); + for (int j = 0; j < mapCount; j++) + { + string checksum = sr.ReadString(); + + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); + if (beatmap != null) + collection.Beatmaps.Add(beatmap); + } + + result.Add(collection); } - - result.Add(collection); } } + catch (Exception e) + { + Logger.Error(e, "Failed to read collections"); + } return result; } + + private readonly object saveLock = new object(); + private int lastSave; + private int saveFailures; + + /// + /// Perform a save with debounce. + /// + private void backgroundSave() + { + var current = Interlocked.Increment(ref lastSave); + Task.Delay(100).ContinueWith(task => + { + if (current != lastSave) + return; + + if (!save()) + backgroundSave(); + }); + } + + private bool save() + { + lock (saveLock) + { + Interlocked.Increment(ref lastSave); + + try + { + // This is NOT thread-safe!! + + using (var sw = new SerializationWriter(host.Storage.GetStream(database_name, FileAccess.Write))) + { + sw.Write(database_version); + sw.Write(collections.Count); + + foreach (var c in collections) + { + sw.Write(c.Name); + sw.Write(c.Beatmaps.Count); + + foreach (var b in c.Beatmaps) + sw.Write(b.MD5Hash); + } + } + + saveFailures = 0; + return true; + } + catch (Exception e) + { + // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). + // Failures are thus only alerted if they exceed a threshold to indicate "actual" errors. + if (++saveFailures >= 10) + { + Logger.Error(e, "Failed to save collections"); + saveFailures = 0; + } + } + + return false; + } + } } public class BeatmapCollection { + /// + /// Invoked whenever any change occurs on this . + /// + public event Action Changed; + public string Name; public readonly BindableList Beatmaps = new BindableList(); @@ -105,7 +195,11 @@ namespace osu.Game.Collections { LastModifyTime = DateTimeOffset.UtcNow; - Beatmaps.CollectionChanged += (_, __) => LastModifyTime = DateTimeOffset.Now; + Beatmaps.CollectionChanged += (_, __) => + { + LastModifyTime = DateTimeOffset.Now; + Changed?.Invoke(); + }; } } } From fd3ab417312e56ded809bb98a86f54f23ddaa0df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:32:08 +0900 Subject: [PATCH 025/137] Save on disposal --- osu.Game/Collections/CollectionManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 3f89866749..5427f6a489 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -176,6 +176,12 @@ namespace osu.Game.Collections return false; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + save(); + } } public class BeatmapCollection From fca03242644bf67d679158588b4224ac0b1bfd57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:34:38 +0900 Subject: [PATCH 026/137] Disallow being able to add dummy beatmap --- osu.Game/Screens/Select/FilterControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 0ae5e70fc2..0b85ae0e6a 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -347,6 +347,8 @@ namespace osu.Game.Screens.Select { Debug.Assert(collectionBeatmaps != null); + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; From ae1de1adcb7b95e84b60f28d84cf67a21f9034f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:42:44 +0900 Subject: [PATCH 027/137] Adjust to prevent runaway errors --- osu.Game/Collections/CollectionManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 5427f6a489..1388a27806 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -159,18 +159,16 @@ namespace osu.Game.Collections } } - saveFailures = 0; + if (saveFailures < 10) + saveFailures = 0; return true; } catch (Exception e) { // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). - // Failures are thus only alerted if they exceed a threshold to indicate "actual" errors. - if (++saveFailures >= 10) - { + // Failures are thus only alerted if they exceed a threshold (once) to indicate "actual" errors having occurred. + if (++saveFailures == 10) Logger.Error(e, "Failed to save collections"); - saveFailures = 0; - } } return false; From 39c304d008ca981ffae95004db43c57789f57435 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:47:42 +0900 Subject: [PATCH 028/137] Adjust error messages --- osu.Game/Collections/CollectionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 1388a27806..e6eed40dfc 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -108,7 +108,7 @@ namespace osu.Game.Collections } catch (Exception e) { - Logger.Error(e, "Failed to read collections"); + Logger.Error(e, "Failed to read collection database."); } return result; @@ -168,7 +168,7 @@ namespace osu.Game.Collections // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). // Failures are thus only alerted if they exceed a threshold (once) to indicate "actual" errors having occurred. if (++saveFailures == 10) - Logger.Error(e, "Failed to save collections"); + Logger.Error(e, "Failed to save collection database!"); } return false; From a56f9d6770e0ea9f3392a59dd2a2e99706c2654e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Sep 2020 00:08:33 +0900 Subject: [PATCH 029/137] Implement collection import --- osu.Game/Collections/CollectionManager.cs | 88 +++++++++++++------ osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 5 +- .../Sections/Maintenance/GeneralSettings.cs | 45 +++++++--- 4 files changed, 99 insertions(+), 41 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index e6eed40dfc..20bf96da9d 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -25,12 +27,13 @@ namespace osu.Game.Collections private const string database_name = "collection.db"; + public readonly BindableList Collections = new BindableList(); + + public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + [Resolved] private GameHost host { get; set; } - public IBindableList Collections => collections; - private readonly BindableList collections = new BindableList(); - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -40,12 +43,12 @@ namespace osu.Game.Collections if (host.Storage.Exists(database_name)) { using (var stream = host.Storage.GetStream(database_name)) - collections.AddRange(readCollection(stream)); + importCollections(readCollections(stream)); } - foreach (var c in collections) + foreach (var c in Collections) c.Changed += backgroundSave; - collections.CollectionChanged += (_, __) => backgroundSave(); + Collections.CollectionChanged += (_, __) => backgroundSave(); } /// @@ -56,26 +59,55 @@ namespace osu.Game.Collections /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - // public Task ImportFromStableAsync() - // { - // var stable = GetStableStorage?.Invoke(); - // - // if (stable == null) - // { - // Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); - // return Task.CompletedTask; - // } - // - // if (!stable.ExistsDirectory(database_name)) - // { - // // This handles situations like when the user does not have a Skins folder - // Logger.Log($"No {database_name} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); - // return Task.CompletedTask; - // } - // - // return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); - // } - private List readCollection(Stream stream) + public Task ImportFromStableAsync() + { + var stable = GetStableStorage?.Invoke(); + + if (stable == null) + { + Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + if (!stable.Exists(database_name)) + { + // This handles situations like when the user does not have a collections.db file + Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + return Task.Run(() => + { + var storage = GetStableStorage(); + + if (storage.Exists(database_name)) + { + using (var stream = storage.GetStream(database_name)) + { + var collection = readCollections(stream); + Schedule(() => importCollections(collection)); + } + } + }); + } + + private void importCollections(List newCollections) + { + foreach (var newCol in newCollections) + { + var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); + if (existing == null) + Collections.Add(existing = new BeatmapCollection { Name = newCol.Name }); + + foreach (var newBeatmap in newCol.Beatmaps) + { + if (!existing.Beatmaps.Contains(newBeatmap)) + existing.Beatmaps.Add(newBeatmap); + } + } + } + + private List readCollections(Stream stream) { var result = new List(); @@ -147,9 +179,9 @@ namespace osu.Game.Collections using (var sw = new SerializationWriter(host.Storage.GetStream(database_name, FileAccess.Write))) { sw.Write(database_version); - sw.Write(collections.Count); + sw.Write(Collections.Count); - foreach (var c in collections) + foreach (var c in Collections) { sw.Write(c.Name); sw.Write(c.Beatmaps.Count); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 053eb01dcd..5008a3cf3b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -549,6 +549,8 @@ namespace osu.Game ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.PresentImport = items => PresentScore(items.First()); + CollectionManager.GetStableStorage = GetStorageForStableInstall; + Container logoContainer; BackButton.Receptor receptor; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3ba164e87f..d512f57ca5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -225,9 +225,8 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - var collectionManager = new CollectionManager(); - dependencies.Cache(collectionManager); - AddInternal(collectionManager); + dependencies.Cache(CollectionManager = new CollectionManager()); + AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 832673703b..21a5ed6b31 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; using osu.Game.Skinning; @@ -19,6 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton importBeatmapsButton; private TriangleButton importScoresButton; private TriangleButton importSkinsButton; + private TriangleButton importCollectionsButton; private TriangleButton deleteBeatmapsButton; private TriangleButton deleteScoresButton; private TriangleButton deleteSkinsButton; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collections, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { @@ -93,20 +95,43 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }); } - AddRange(new Drawable[] + Add(deleteSkinsButton = new DangerousSettingsButton { - deleteSkinsButton = new DangerousSettingsButton + Text = "Delete ALL skins", + Action = () => { - Text = "Delete ALL skins", + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => + { + deleteSkinsButton.Enabled.Value = false; + Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); + })); + } + }); + + if (collections.SupportsImportFromStable) + { + Add(importCollectionsButton = new SettingsButton + { + Text = "Import collections from stable", Action = () => { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => - { - deleteSkinsButton.Enabled.Value = false; - Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); - })); + importCollectionsButton.Enabled.Value = false; + collections.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } - }, + }); + } + + Add(new DangerousSettingsButton + { + Text = "Delete ALL collections", + Action = () => + { + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collections.Collections.Clear())); + } + }); + + AddRange(new Drawable[] + { restoreButton = new SettingsButton { Text = "Restore all hidden difficulties", From e0328445709f5218befafe0eb091ab7d0e1e738a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 19:38:05 +0900 Subject: [PATCH 030/137] Start with a fresh beatmap when entering editor from main menu --- osu.Game/Beatmaps/BeatmapManager.cs | 25 +++++++++++++++++++ .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 3 +++ .../Menus/ScreenSelectionTabControl.cs | 2 -- osu.Game/Screens/Edit/Editor.cs | 9 +++++++ osu.Game/Screens/Menu/MainMenu.cs | 6 ++++- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0cadcf5947..9289ed29d9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -27,6 +27,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Users; using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Beatmaps @@ -94,6 +95,30 @@ namespace osu.Game.Beatmaps protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; + public WorkingBeatmap CreateNew(RulesetInfo ruleset) + { + var set = new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Artist = "unknown", + Title = "unknown", + Author = User.SYSTEM_USER, + }, + Beatmaps = new List + { + new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + Ruleset = ruleset + } + } + }; + + var working = Import(set).Result; + return GetWorkingBeatmap(working.Beatmaps.First()); + } + protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) { if (archive != null) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 92199789ec..63b1647f33 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -33,6 +33,9 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() { + if (BeatmapInfo.Path == null) + return BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap()).Beatmap; + try { using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) diff --git a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs index 089da4f222..b8bc5cdf36 100644 --- a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Edit.Components.Menus Height = 1, Colour = Color4.White.Opacity(0.2f), }); - - Current.Value = EditorScreenMode.Compose; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e178459d5c..dfba5f457b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -89,6 +89,14 @@ namespace osu.Game.Screens.Edit // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); + bool isNewBeatmap = false; + + if (Beatmap.Value is DummyWorkingBeatmap) + { + isNewBeatmap = true; + Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value); + } + try { playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); @@ -148,6 +156,7 @@ namespace osu.Game.Screens.Edit Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, + Mode = { Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose }, Items = new[] { new MenuItem("File") diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index fac9e9eb49..e0ac19cbaf 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -98,7 +98,11 @@ namespace osu.Game.Screens.Menu { buttons = new ButtonSystem { - OnEdit = delegate { this.Push(new Editor()); }, + OnEdit = delegate + { + Beatmap.SetDefault(); + this.Push(new Editor()); + }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, OnExit = confirmAndExit, From faf9b0a52864b2d5f8beedb12ab02cefb6bdb15a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 15:48:13 +0900 Subject: [PATCH 031/137] Fix hard crash when trying to retrieve a beatmap's track when no file is present --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 63b1647f33..9e4c29a8d4 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -70,6 +70,9 @@ namespace osu.Game.Beatmaps protected override Track GetBeatmapTrack() { + if (Metadata?.AudioFile == null) + return null; + try { return trackStore.Get(getPathForFile(Metadata.AudioFile)); @@ -83,6 +86,9 @@ namespace osu.Game.Beatmaps protected override Waveform GetWaveform() { + if (Metadata?.AudioFile == null) + return null; + try { var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); From 218cc39a4c46ce467264d11600a15b1e169795de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 15:49:21 +0900 Subject: [PATCH 032/137] Avoid throwing exceptions when MutatePath is called with null path --- osu.Game/IO/WrappedStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 1dd3afbfae..766e36ef14 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -25,7 +25,7 @@ namespace osu.Game.IO this.subPath = subPath; } - protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path; + protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) && !string.IsNullOrEmpty(path) ? Path.Combine(subPath, path) : path; protected virtual void ChangeTargetStorage(Storage newStorage) { From e80ef341d2663ea07c272154b8ddb1e0bde13467 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 15:50:08 +0900 Subject: [PATCH 033/137] Allow UpdateFile to be called when a previous file doesn't exist --- osu.Game/Database/ArchiveModelManager.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 915d980d24..49d7edd56c 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -397,15 +397,24 @@ namespace osu.Game.Database } } + /// + /// Update an existing file, or create a new entry if not already part of the 's files. + /// + /// The item to operate on. + /// The file model to be updated or added. + /// The new file contents. public void UpdateFile(TModel model, TFileModel file, Stream contents) { using (var usage = ContextFactory.GetForWrite()) { // Dereference the existing file info, since the file model will be removed. - Files.Dereference(file.FileInfo); + if (file.FileInfo != null) + { + Files.Dereference(file.FileInfo); - // Remove the file model. - usage.Context.Set().Remove(file); + // Remove the file model. + usage.Context.Set().Remove(file); + } // Add the new file info and containing file model. model.Files.Remove(file); From e337e6b3b0d804c40aac0667471c52a205584a20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 18:55:49 +0900 Subject: [PATCH 034/137] Use a more correct filename when saving --- osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9289ed29d9..fee7a71df4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -237,10 +237,20 @@ namespace osu.Game.Beatmaps using (ContextFactory.GetForWrite()) { var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); + var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; + + // metadata may have changed; update the path with the standard format. + beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}]"; + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo + { + Filename = beatmapInfo.Path + }; + stream.Seek(0, SeekOrigin.Begin); - UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); + UpdateFile(setInfo, fileInfo, stream); } } From d849f7f2b5b83b76b1e2a1803aadcbb10f6e3d1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 18:56:49 +0900 Subject: [PATCH 035/137] Use the local user's username when saving a new beatmap --- osu.Game/Beatmaps/BeatmapManager.cs | 18 +++++++++++------- osu.Game/Screens/Edit/Editor.cs | 14 +++++++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index fee7a71df4..325e6c6e98 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -97,20 +97,24 @@ namespace osu.Game.Beatmaps public WorkingBeatmap CreateNew(RulesetInfo ruleset) { + var metadata = new BeatmapMetadata + { + Artist = "artist", + Title = "title", + Author = User.SYSTEM_USER, + }; + var set = new BeatmapSetInfo { - Metadata = new BeatmapMetadata - { - Artist = "unknown", - Title = "unknown", - Author = User.SYSTEM_USER, - }, + Metadata = metadata, Beatmaps = new List { new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(), - Ruleset = ruleset + Ruleset = ruleset, + Metadata = metadata, + Version = "difficulty" } } }; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index dfba5f457b..b8c1932186 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -28,6 +28,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Online.API; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; @@ -72,6 +73,9 @@ namespace osu.Game.Screens.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + [Resolved] + private IAPIProvider api { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, GameHost host) { @@ -95,6 +99,7 @@ namespace osu.Game.Screens.Edit { isNewBeatmap = true; Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value); + Beatmap.Value.BeatmapSetInfo.Metadata.Author = api.LocalUser.Value; } try @@ -408,7 +413,14 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap); + private void saveBeatmap() + { + // apply any set-level metadata changes. + beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); + + // save the loaded beatmap's data stream. + beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap); + } private void exportBeatmap() { From 58e84760b959362ee49fd98d36b8222f30298f70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:17:07 +0900 Subject: [PATCH 036/137] Fix path empty string check causing regression in behaviour --- osu.Game/IO/WrappedStorage.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 766e36ef14..5b2549d2ee 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -25,7 +25,13 @@ namespace osu.Game.IO this.subPath = subPath; } - protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) && !string.IsNullOrEmpty(path) ? Path.Combine(subPath, path) : path; + protected virtual string MutatePath(string path) + { + if (path == null) + return null; + + return !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path; + } protected virtual void ChangeTargetStorage(Storage newStorage) { From a555407f3772353b2b4f8b9b3a93179ec2212585 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:20:42 +0900 Subject: [PATCH 037/137] Fix various test failures due to missing beatmap info in empty beatmap --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 9e4c29a8d4..11b6ad8c29 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() { if (BeatmapInfo.Path == null) - return BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap()).Beatmap; + return BeatmapInfo?.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap { BeatmapInfo = BeatmapInfo }).Beatmap; try { diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 6a161e6e04..aefeb48453 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps { const double excess_length = 1000; - var lastObject = Beatmap.HitObjects.LastOrDefault(); + var lastObject = Beatmap?.HitObjects.LastOrDefault(); double length; From 1c1c583d3b9bf0ed2b306f7fef8ebae0bd11f1b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:31:40 +0900 Subject: [PATCH 038/137] Fix regression in file update logic (filename set too early) --- osu.Game/Beatmaps/BeatmapManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 325e6c6e98..1456e7487b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -243,15 +243,15 @@ namespace osu.Game.Beatmaps var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; + // grab the original file (or create a new one if not found). + var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); + // metadata may have changed; update the path with the standard format. beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}]"; - beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); - var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo - { - Filename = beatmapInfo.Path - }; + // update existing or populate new file's filename. + fileInfo.Filename = beatmapInfo.Path; stream.Seek(0, SeekOrigin.Begin); UpdateFile(setInfo, fileInfo, stream); From c9a73926a68cd4104eb719f8b365be601c67373c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:38:01 +0900 Subject: [PATCH 039/137] Add basic test coverage --- .../Beatmaps/IO/ImportBeatmapTest.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0151678db3..4547613b5a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.IO; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Resources; using SharpCompress.Archives; @@ -756,6 +757,63 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public void TestCreateNewEmptyBeatmap() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + { + try + { + var osu = loadOsu(host); + var manager = osu.Dependencies.Get(); + + var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + + manager.Save(working.BeatmapInfo, working.Beatmap); + + var retrievedSet = manager.GetAllUsableBeatmapSets()[0]; + + // Check that the new file is referenced correctly by attempting a retrieval + Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap; + Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestCreateNewBeatmapWithObject() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + { + try + { + var osu = loadOsu(host); + var manager = osu.Dependencies.Get(); + + var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + + ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 }); + + manager.Save(working.BeatmapInfo, working.Beatmap); + + var retrievedSet = manager.GetAllUsableBeatmapSets()[0]; + + // Check that the new file is referenced correctly by attempting a retrieval + Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap; + Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1)); + Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000)); + } + finally + { + host.Exit(); + } + } + } + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) { var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); From d32b77f045a120ee21645baff1209f5c9215118c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 21:33:25 +0900 Subject: [PATCH 040/137] Add missing extension to filename Co-authored-by: Dan Balasescu --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1456e7487b..12add76b46 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -247,7 +247,7 @@ namespace osu.Game.Beatmaps var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); // metadata may have changed; update the path with the standard format. - beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}]"; + beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu"; beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); // update existing or populate new file's filename. From f14a82e3a927950e23631ed1c5eeb430c94c8957 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 13:13:53 +0900 Subject: [PATCH 041/137] Remove unnecessary conversion --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 11b6ad8c29..362c99ea3f 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() { if (BeatmapInfo.Path == null) - return BeatmapInfo?.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap { BeatmapInfo = BeatmapInfo }).Beatmap; + return new Beatmap { BeatmapInfo = BeatmapInfo }; try { From d3fbc7cc53ea92a28e76ed07f11a6edc855c584e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 13:16:35 +0900 Subject: [PATCH 042/137] Use more direct reference in tests --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 4547613b5a..6c60d7b467 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -767,7 +767,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo); manager.Save(working.BeatmapInfo, working.Beatmap); @@ -794,7 +794,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo); ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 }); From fba253f131e7f5d279a0f011d404ef7685ab2c1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 13:17:43 +0900 Subject: [PATCH 043/137] Take user argument in CreateNew method parameters --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 5 +++-- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 6c60d7b467..cef8105490 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -18,6 +18,7 @@ using osu.Game.IO; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Resources; +using osu.Game.Users; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; @@ -767,7 +768,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = manager.CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); manager.Save(working.BeatmapInfo, working.Beatmap); @@ -794,7 +795,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = manager.CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 }); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 12add76b46..844af31a16 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -95,13 +95,13 @@ namespace osu.Game.Beatmaps protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; - public WorkingBeatmap CreateNew(RulesetInfo ruleset) + public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user) { var metadata = new BeatmapMetadata { Artist = "artist", Title = "title", - Author = User.SYSTEM_USER, + Author = user, }; var set = new BeatmapSetInfo diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b8c1932186..ef497e3246 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -98,8 +98,7 @@ namespace osu.Game.Screens.Edit if (Beatmap.Value is DummyWorkingBeatmap) { isNewBeatmap = true; - Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value); - Beatmap.Value.BeatmapSetInfo.Metadata.Author = api.LocalUser.Value; + Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); } try From 42895e27b6f1188e6e623d5a921c99f4b4f0038f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:10:14 +0300 Subject: [PATCH 044/137] Expose track change results on the methods --- osu.Game/Overlays/MusicController.cs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 119aad5226..ea72ef0b84 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -207,7 +207,18 @@ namespace osu.Game.Overlays /// /// Play the previous track or restart the current track if it's current time below . /// - public void PreviousTrack() => Schedule(() => prev()); + /// + /// Invoked when the operation has been performed successfully. + /// The result isn't returned directly to the caller because + /// the operation is scheduled and isn't performed immediately. + /// + /// A of the operation. + public ScheduledDelegate PreviousTrack(Action onSuccess = null) => Schedule(() => + { + PreviousTrackResult res = prev(); + if (res != PreviousTrackResult.None) + onSuccess?.Invoke(res); + }); /// /// Play the previous track or restart the current track if it's current time below . @@ -243,7 +254,18 @@ namespace osu.Game.Overlays /// /// Play the next random or playlist track. /// - public void NextTrack() => Schedule(() => next()); + /// + /// Invoked when the operation has been performed successfully. + /// The result isn't returned directly to the caller because + /// the operation is scheduled and isn't performed immediately. + /// + /// A of the operation. + public ScheduledDelegate NextTrack(Action onSuccess = null) => Schedule(() => + { + bool res = next(); + if (res) + onSuccess?.Invoke(); + }); private bool next() { From 001509df55015a32b5b6f6d582c805f0a4f459f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:22:37 +0300 Subject: [PATCH 045/137] Move music global action handling to an own component Due to requiring components that are added at an OsuGame-level --- osu.Game/OsuGame.cs | 2 + osu.Game/Overlays/Music/MusicActionHandler.cs | 82 +++++++++++++++++++ osu.Game/Overlays/MusicController.cs | 56 +------------ 3 files changed, 85 insertions(+), 55 deletions(-) create mode 100644 osu.Game/Overlays/Music/MusicActionHandler.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 164a40c6a5..a73469d836 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -38,6 +38,7 @@ using osu.Game.Input; using osu.Game.Overlays.Notifications; using osu.Game.Input.Bindings; using osu.Game.Online.Chat; +using osu.Game.Overlays.Music; using osu.Game.Skinning; using osuTK.Graphics; using osu.Game.Overlays.Volume; @@ -647,6 +648,7 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); + Add(new MusicActionHandler()); // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; diff --git a/osu.Game/Overlays/Music/MusicActionHandler.cs b/osu.Game/Overlays/Music/MusicActionHandler.cs new file mode 100644 index 0000000000..17aa4c1d32 --- /dev/null +++ b/osu.Game/Overlays/Music/MusicActionHandler.cs @@ -0,0 +1,82 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.OSD; + +namespace osu.Game.Overlays.Music +{ + /// + /// Handles relating to music playback, and displays a via the cached accordingly. + /// + public class MusicActionHandler : Component, IKeyBindingHandler + { + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private MusicController musicController { get; set; } + + [Resolved] + private OnScreenDisplay onScreenDisplay { get; set; } + + public bool OnPressed(GlobalAction action) + { + if (beatmap.Disabled) + return false; + + switch (action) + { + case GlobalAction.MusicPlay: + if (musicController.TogglePause()) + onScreenDisplay.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); + + return true; + + case GlobalAction.MusicNext: + musicController.NextTrack(() => + { + onScreenDisplay.Display(new MusicActionToast("Next track")); + }).RunTask(); + + return true; + + case GlobalAction.MusicPrev: + musicController.PreviousTrack(res => + { + switch (res) + { + case PreviousTrackResult.Restart: + onScreenDisplay.Display(new MusicActionToast("Restart track")); + break; + + case PreviousTrackResult.Previous: + onScreenDisplay.Display(new MusicActionToast("Previous track")); + break; + } + }).RunTask(); + + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + + private class MusicActionToast : Toast + { + public MusicActionToast(string action) + : base("Music Playback", action, string.Empty) + { + } + } + } +} diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ea72ef0b84..69722a8c0c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -12,12 +12,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; using osu.Framework.Utils; using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Input.Bindings; -using osu.Game.Overlays.OSD; using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays @@ -25,7 +22,7 @@ namespace osu.Game.Overlays /// /// Handles playback of the global music track. /// - public class MusicController : CompositeDrawable, IKeyBindingHandler + public class MusicController : CompositeDrawable { [Resolved] private BeatmapManager beatmaps { get; set; } @@ -62,9 +59,6 @@ namespace osu.Game.Overlays [Resolved] private IBindable> mods { get; set; } - [Resolved(canBeNull: true)] - private OnScreenDisplay onScreenDisplay { get; set; } - [NotNull] public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); @@ -428,54 +422,6 @@ namespace osu.Game.Overlays mod.ApplyToTrack(CurrentTrack); } } - - public bool OnPressed(GlobalAction action) - { - if (beatmap.Disabled) - return false; - - switch (action) - { - case GlobalAction.MusicPlay: - if (TogglePause()) - onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track")); - return true; - - case GlobalAction.MusicNext: - if (next()) - onScreenDisplay?.Display(new MusicControllerToast("Next track")); - - return true; - - case GlobalAction.MusicPrev: - switch (prev()) - { - case PreviousTrackResult.Restart: - onScreenDisplay?.Display(new MusicControllerToast("Restart track")); - break; - - case PreviousTrackResult.Previous: - onScreenDisplay?.Display(new MusicControllerToast("Previous track")); - break; - } - - return true; - } - - return false; - } - - public void OnReleased(GlobalAction action) - { - } - - public class MusicControllerToast : Toast - { - public MusicControllerToast(string action) - : base("Music Playback", action, string.Empty) - { - } - } } public enum TrackChangeDirection From 4d9a06bde993bd416604e0b13e71a4fedf72873f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:23:06 +0300 Subject: [PATCH 046/137] Expose the global binding container to OsuGameTestScene --- osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs | 3 +++ osu.Game/OsuGameBase.cs | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index c4acf4f7da..e29d23ba75 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -14,6 +14,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -109,6 +110,8 @@ namespace osu.Game.Tests.Visual.Navigation public new OsuConfigManager LocalConfig => base.LocalConfig; + public new GlobalActionContainer GlobalBinding => base.GlobalBinding; + public new Bindable Beatmap => base.Beatmap; public new Bindable Ruleset => base.Ruleset; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 51b9b7278d..8e01bda6ec 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -64,6 +64,8 @@ namespace osu.Game protected FileStore FileStore; + protected GlobalActionContainer GlobalBinding; + protected KeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -250,10 +252,8 @@ namespace osu.Game AddInternal(apiAccess); AddInternal(RulesetConfigCache); - GlobalActionContainer globalBinding; - MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; - MenuCursorContainer.Child = globalBinding = new GlobalActionContainer(this) + MenuCursorContainer.Child = GlobalBinding = new GlobalActionContainer(this) { RelativeSizeAxes = Axes.Both, Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } @@ -261,8 +261,8 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); - KeyBindingStore.Register(globalBinding); - dependencies.Cache(globalBinding); + KeyBindingStore.Register(GlobalBinding); + dependencies.Cache(GlobalBinding); PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); From 314031d56d342f722d998d0897769ea2de4bde9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:23:34 +0300 Subject: [PATCH 047/137] Add test cases ensuring music actions are handled from a game instance --- .../Menus/TestSceneMusicActionHandling.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs new file mode 100644 index 0000000000..9121309489 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Navigation; + +namespace osu.Game.Tests.Visual.Menus +{ + public class TestSceneMusicActionHandling : OsuGameTestScene + { + [Test] + public void TestMusicPlayAction() + { + AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); + AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused); + AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused); + } + + [Test] + public void TestMusicNavigationActions() + { + int importId = 0; + Queue<(WorkingBeatmap working, TrackChangeDirection dir)> trackChangeQueue = null; + + // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo + { + Beatmaps = new List + { + new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + } + }, + Metadata = new BeatmapMetadata + { + Artist = $"a test map {importId++}", + Title = "title", + } + }).Wait(), 5); + + AddStep("import beatmap with track", () => + { + var setWithTrack = Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result; + Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); + }); + + AddStep("bind to track change", () => + { + trackChangeQueue = new Queue<(WorkingBeatmap working, TrackChangeDirection dir)>(); + Game.MusicController.TrackChanged += (working, dir) => trackChangeQueue.Enqueue((working, dir)); + }); + + AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); + AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000); + + AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddAssert("no track change", () => trackChangeQueue.Count == 0); + AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); + + AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddAssert("track changed to previous", () => + trackChangeQueue.Count == 1 && + trackChangeQueue.Dequeue().dir == TrackChangeDirection.Prev); + + AddStep("trigger music next action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); + AddAssert("track changed to next", () => + trackChangeQueue.Count == 1 && + trackChangeQueue.Dequeue().dir == TrackChangeDirection.Next); + } + } +} From 0500f24a1dbdede80d403e1f9b03fea9cfabc901 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:24:07 +0300 Subject: [PATCH 048/137] Remove now-redundant test case --- .../TestSceneNowPlayingOverlay.cs | 58 +------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index c14a1ddbf2..475ab0c414 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -1,18 +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.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Graphics; -using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.Overlays; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.UserInterface { @@ -24,14 +17,9 @@ namespace osu.Game.Tests.Visual.UserInterface private NowPlayingOverlay nowPlayingOverlay; - private RulesetStore rulesets; - [BackgroundDependencyLoader] - private void load(AudioManager audio, GameHost host) + private void load() { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); - Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); nowPlayingOverlay = new NowPlayingOverlay @@ -51,49 +39,5 @@ namespace osu.Game.Tests.Visual.UserInterface AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); AddStep(@"hide", () => nowPlayingOverlay.Hide()); } - - private BeatmapManager manager { get; set; } - - private int importId; - - [Test] - public void TestPrevTrackBehavior() - { - // ensure we have at least two beatmaps available. - AddRepeatStep("import beatmap", () => manager.Import(new BeatmapSetInfo - { - Beatmaps = new List - { - new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - } - }, - Metadata = new BeatmapMetadata - { - Artist = $"a test map {importId++}", - Title = "title", - } - }).Wait(), 5); - - WorkingBeatmap currentBeatmap = null; - - AddStep("import beatmap with track", () => - { - var setWithTrack = manager.Import(TestResources.GetTestBeatmapForImport()).Result; - Beatmap.Value = currentBeatmap = manager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); - }); - - AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000)); - AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack.CurrentTime > 5000); - - AddStep(@"Set previous", () => musicController.PreviousTrack()); - - AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value); - AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack.CurrentTime < 5000); - - AddStep(@"Set previous", () => musicController.PreviousTrack()); - AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value); - } } } From ebd11ae0b7a3afe13d0054ed845dc42d9efa944c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 5 Sep 2020 03:52:07 +0900 Subject: [PATCH 049/137] Add a collection management dialog --- .../Collections/TestSceneCollectionDialog.cs | 26 +++ osu.Game/Collections/CollectionDialog.cs | 125 ++++++++++++++ osu.Game/Collections/CollectionList.cs | 27 ++++ osu.Game/Collections/CollectionListItem.cs | 152 ++++++++++++++++++ .../Collections/DeleteCollectionDialog.cs | 36 +++++ osu.Game/OsuGame.cs | 2 + .../Carousel/DrawableCarouselBeatmap.cs | 14 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 14 +- 8 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs create mode 100644 osu.Game/Collections/CollectionDialog.cs create mode 100644 osu.Game/Collections/CollectionList.cs create mode 100644 osu.Game/Collections/CollectionListItem.cs create mode 100644 osu.Game/Collections/DeleteCollectionDialog.cs diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs new file mode 100644 index 0000000000..f810361610 --- /dev/null +++ b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs @@ -0,0 +1,26 @@ +// 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.Graphics.Containers; +using osu.Game.Collections; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Collections +{ + public class TestSceneCollectionDialog : OsuTestScene + { + [Cached] + private DialogOverlay dialogOverlay; + + public TestSceneCollectionDialog() + { + Children = new Drawable[] + { + new CollectionDialog { State = { Value = Visibility.Visible } }, + dialogOverlay = new DialogOverlay() + }; + } + } +} diff --git a/osu.Game/Collections/CollectionDialog.cs b/osu.Game/Collections/CollectionDialog.cs new file mode 100644 index 0000000000..e911b507e5 --- /dev/null +++ b/osu.Game/Collections/CollectionDialog.cs @@ -0,0 +1,125 @@ +// 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.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Collections +{ + public class CollectionDialog : OsuFocusedOverlayContainer + { + private const double enter_duration = 500; + private const double exit_duration = 200; + + [Resolved] + private CollectionManager collectionManager { get; set; } + + public CollectionDialog() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(0.5f, 0.8f); + + Masking = true; + CornerRadius = 10; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 50), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 50), + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Manage collections", + Font = OsuFont.GetFont(size: 30), + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDarker + }, + new CollectionList + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = collectionManager.Collections } + } + } + } + }, + new Drawable[] + { + new OsuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Padding = new MarginPadding(10), + Text = "Create new collection", + Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = "My new collection" }) + }, + }, + } + } + } + }; + } + + protected override void PopIn() + { + base.PopIn(); + + this.FadeIn(enter_duration, Easing.OutQuint); + this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutElastic); + } + + protected override void PopOut() + { + base.PopOut(); + + this.FadeOut(exit_duration, Easing.OutQuint); + this.ScaleTo(0.9f, exit_duration); + } + } +} diff --git a/osu.Game/Collections/CollectionList.cs b/osu.Game/Collections/CollectionList.cs new file mode 100644 index 0000000000..990f82e702 --- /dev/null +++ b/osu.Game/Collections/CollectionList.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Collections +{ + public class CollectionList : OsuRearrangeableListContainer + { + protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => + { + d.ScrollbarVisible = false; + }); + + protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> + { + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + Spacing = new Vector2(0, 2) + }; + + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new CollectionListItem(item); + } +} diff --git a/osu.Game/Collections/CollectionListItem.cs b/osu.Game/Collections/CollectionListItem.cs new file mode 100644 index 0000000000..527ed57cd0 --- /dev/null +++ b/osu.Game/Collections/CollectionListItem.cs @@ -0,0 +1,152 @@ +// 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.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Collections +{ + public class CollectionListItem : OsuRearrangeableListItem + { + private const float item_height = 35; + + public CollectionListItem(BeatmapCollection item) + : base(item) + { + Padding = new MarginPadding { Right = 20 }; + } + + protected override Drawable CreateContent() => new ItemContent(Model); + + private class ItemContent : CircularContainer + { + private readonly BeatmapCollection collection; + + private ItemTextBox textBox; + + public ItemContent(BeatmapCollection collection) + { + this.collection = collection; + + RelativeSizeAxes = Axes.X; + Height = item_height; + Masking = true; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new DeleteButton(collection) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = item_height / 2 }, + Children = new Drawable[] + { + textBox = new ItemTextBox + { + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + CornerRadius = item_height / 2, + Text = collection.Name + }, + } + }, + }; + } + } + + private class ItemTextBox : OsuTextBox + { + protected override float LeftRightPadding => item_height / 2; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundUnfocused = colours.GreySeafoamDarker.Darken(0.5f); + BackgroundFocused = colours.GreySeafoam; + } + } + + private class DeleteButton : CompositeDrawable + { + public Func IsTextBoxHovered; + + [Resolved(CanBeNull = true)] + private DialogOverlay dialogOverlay { get; set; } + + private readonly BeatmapCollection collection; + + private Drawable background; + + public DeleteButton(BeatmapCollection collection) + { + this.collection = collection; + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + + Alpha = 0.1f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Red + }, + new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -6, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Trash + } + }; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); + + protected override bool OnHover(HoverEvent e) + { + this.FadeTo(1f, 100, Easing.Out); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.FadeTo(0.1f, 100); + } + + protected override bool OnClick(ClickEvent e) + { + background.FlashColour(Color4.White, 150); + dialogOverlay?.Push(new DeleteCollectionDialog(collection)); + return true; + } + } + } +} diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs new file mode 100644 index 0000000000..a7677e9de2 --- /dev/null +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -0,0 +1,36 @@ +// 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.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Collections +{ + public class DeleteCollectionDialog : PopupDialog + { + [Resolved] + private CollectionManager collectionManager { get; set; } + + public DeleteCollectionDialog(BeatmapCollection collection) + { + HeaderText = "Confirm deletion of"; + BodyText = collection.Name; + + Icon = FontAwesome.Regular.TrashAlt; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = () => collectionManager.Collections.Remove(collection) + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!", + }, + }; + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5008a3cf3b..1f69888d2e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -31,6 +31,7 @@ using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -632,6 +633,7 @@ namespace osu.Game loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements + loadComponentSingleFile(new CollectionDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1bd4447248..fe4c95f4e3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private CollectionManager collectionManager { get; set; } + [Resolved(CanBeNull = true)] + private CollectionDialog collectionDialog { get; set; } + private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -224,12 +227,11 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - items.Add(new OsuMenuItem("Add to...") - { - Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) - .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) - .ToArray() - }); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); + if (collectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + + items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); return items.ToArray(); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e05b5ee951..8a9b0dc5b3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -38,6 +38,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private CollectionManager collectionManager { get; set; } + [Resolved(CanBeNull = true)] + private CollectionDialog collectionDialog { get; set; } + private readonly BeatmapSetInfo beatmapSet; public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) @@ -145,12 +148,11 @@ namespace osu.Game.Screens.Select.Carousel if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); - items.Add(new OsuMenuItem("Add all to...") - { - Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) - .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) - .ToArray() - }); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); + if (collectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + + items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); return items.ToArray(); } From 345fb9d8e02f057c33a8430126463285c9a27c1e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 5 Sep 2020 03:55:43 +0900 Subject: [PATCH 050/137] Rename classes --- .../Visual/Collections/TestSceneCollectionDialog.cs | 2 +- .../{CollectionManager.cs => BeatmapCollectionManager.cs} | 2 +- osu.Game/Collections/DeleteCollectionDialog.cs | 2 +- .../{CollectionList.cs => DrawableCollectionList.cs} | 4 ++-- ...ollectionListItem.cs => DrawableCollectionListItem.cs} | 4 ++-- .../{CollectionDialog.cs => ManageCollectionDialog.cs} | 8 ++++---- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- .../Settings/Sections/Maintenance/GeneralSettings.cs | 8 ++++---- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 8 ++++---- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 8 ++++---- osu.Game/Screens/Select/FilterControl.cs | 2 +- 12 files changed, 27 insertions(+), 27 deletions(-) rename osu.Game/Collections/{CollectionManager.cs => BeatmapCollectionManager.cs} (99%) rename osu.Game/Collections/{CollectionList.cs => DrawableCollectionList.cs} (83%) rename osu.Game/Collections/{CollectionListItem.cs => DrawableCollectionListItem.cs} (96%) rename osu.Game/Collections/{CollectionDialog.cs => ManageCollectionDialog.cs} (94%) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs index f810361610..247d27f67a 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Collections { Children = new Drawable[] { - new CollectionDialog { State = { Value = Visibility.Visible } }, + new ManageCollectionDialog { State = { Value = Visibility.Visible } }, dialogOverlay = new DialogOverlay() }; } diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs similarity index 99% rename from osu.Game/Collections/CollectionManager.cs rename to osu.Game/Collections/BeatmapCollectionManager.cs index 20bf96da9d..0b066708d8 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -18,7 +18,7 @@ using osu.Game.IO.Legacy; namespace osu.Game.Collections { - public class CollectionManager : CompositeDrawable + public class BeatmapCollectionManager : CompositeDrawable { /// /// Database version in YYYYMMDD format (matching stable). diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index a7677e9de2..81bedca638 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -10,7 +10,7 @@ namespace osu.Game.Collections public class DeleteCollectionDialog : PopupDialog { [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } public DeleteCollectionDialog(BeatmapCollection collection) { diff --git a/osu.Game/Collections/CollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs similarity index 83% rename from osu.Game/Collections/CollectionList.cs rename to osu.Game/Collections/DrawableCollectionList.cs index 990f82e702..ab146c17b6 100644 --- a/osu.Game/Collections/CollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -8,7 +8,7 @@ using osuTK; namespace osu.Game.Collections { - public class CollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer { protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { @@ -22,6 +22,6 @@ namespace osu.Game.Collections Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new CollectionListItem(item); + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new DrawableCollectionListItem(item); } } diff --git a/osu.Game/Collections/CollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs similarity index 96% rename from osu.Game/Collections/CollectionListItem.cs rename to osu.Game/Collections/DrawableCollectionListItem.cs index 527ed57cd0..67756cdb43 100644 --- a/osu.Game/Collections/CollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -18,11 +18,11 @@ using osuTK.Graphics; namespace osu.Game.Collections { - public class CollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; - public CollectionListItem(BeatmapCollection item) + public DrawableCollectionListItem(BeatmapCollection item) : base(item) { Padding = new MarginPadding { Right = 20 }; diff --git a/osu.Game/Collections/CollectionDialog.cs b/osu.Game/Collections/ManageCollectionDialog.cs similarity index 94% rename from osu.Game/Collections/CollectionDialog.cs rename to osu.Game/Collections/ManageCollectionDialog.cs index e911b507e5..6a0d815e43 100644 --- a/osu.Game/Collections/CollectionDialog.cs +++ b/osu.Game/Collections/ManageCollectionDialog.cs @@ -13,15 +13,15 @@ using osuTK; namespace osu.Game.Collections { - public class CollectionDialog : OsuFocusedOverlayContainer + public class ManageCollectionDialog : OsuFocusedOverlayContainer { private const double enter_duration = 500; private const double exit_duration = 200; [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } - public CollectionDialog() + public ManageCollectionDialog() { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -79,7 +79,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Colour = colours.GreySeafoamDarker }, - new CollectionList + new DrawableCollectionList { RelativeSizeAxes = Axes.Both, Items = { BindTarget = collectionManager.Collections } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1f69888d2e..2f8f1b2f17 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -633,7 +633,7 @@ namespace osu.Game loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements - loadComponentSingleFile(new CollectionDialog(), overlayContent.Add, true); + loadComponentSingleFile(new ManageCollectionDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d512f57ca5..a7efecadfd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -56,7 +56,7 @@ namespace osu.Game protected BeatmapManager BeatmapManager; - protected CollectionManager CollectionManager; + protected BeatmapCollectionManager CollectionManager; protected ScoreManager ScoreManager; @@ -225,7 +225,7 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new CollectionManager()); + dependencies.Cache(CollectionManager = new BeatmapCollectionManager()); AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 21a5ed6b31..74f9920ae0 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collections, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, BeatmapCollectionManager collectionManager, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { @@ -108,7 +108,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (collections.SupportsImportFromStable) + if (collectionManager.SupportsImportFromStable) { Add(importCollectionsButton = new SettingsButton { @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importCollectionsButton.Enabled.Value = false; - collections.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = "Delete ALL collections", Action = () => { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collections.Collections.Clear())); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collectionManager.Collections.Clear())); } }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index fe4c95f4e3..8fd428d26e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -49,10 +49,10 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapDifficultyManager difficultyManager { get; set; } [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private CollectionDialog collectionDialog { get; set; } + private ManageCollectionDialog manageCollectionDialog { get; set; } private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -228,8 +228,8 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (collectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + if (manageCollectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8a9b0dc5b3..12c6b320e9 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -36,10 +36,10 @@ namespace osu.Game.Screens.Select.Carousel private DialogOverlay dialogOverlay { get; set; } [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private CollectionDialog collectionDialog { get; set; } + private ManageCollectionDialog manageCollectionDialog { get; set; } private readonly BeatmapSetInfo beatmapSet; @@ -149,8 +149,8 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (collectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + if (manageCollectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 0b85ae0e6a..0db24f0738 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(CollectionManager collectionManager) + private void load(BeatmapCollectionManager collectionManager) { collections.BindTo(collectionManager.Collections); collections.CollectionChanged += (_, __) => collectionsChanged(); From 4b4dd02942c6e8d1ec7faa6490cce8051958c47c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 5 Sep 2020 04:43:51 +0900 Subject: [PATCH 051/137] Make collection name a bindable --- osu.Game/Collections/BeatmapCollectionManager.cs | 8 ++++---- osu.Game/Collections/DeleteCollectionDialog.cs | 2 +- osu.Game/Collections/DrawableCollectionListItem.cs | 2 +- osu.Game/Collections/ManageCollectionDialog.cs | 5 ++++- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 2 +- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 0b066708d8..3e5976300f 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -97,7 +97,7 @@ namespace osu.Game.Collections { var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); if (existing == null) - Collections.Add(existing = new BeatmapCollection { Name = newCol.Name }); + Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); foreach (var newBeatmap in newCol.Beatmaps) { @@ -122,7 +122,7 @@ namespace osu.Game.Collections for (int i = 0; i < collectionCount; i++) { - var collection = new BeatmapCollection { Name = sr.ReadString() }; + var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) @@ -183,7 +183,7 @@ namespace osu.Game.Collections foreach (var c in Collections) { - sw.Write(c.Name); + sw.Write(c.Name.Value); sw.Write(c.Beatmaps.Count); foreach (var b in c.Beatmaps) @@ -221,7 +221,7 @@ namespace osu.Game.Collections /// public event Action Changed; - public string Name; + public readonly Bindable Name = new Bindable(); public readonly BindableList Beatmaps = new BindableList(); diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 81bedca638..f2b8de7c1e 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -15,7 +15,7 @@ namespace osu.Game.Collections public DeleteCollectionDialog(BeatmapCollection collection) { HeaderText = "Confirm deletion of"; - BodyText = collection.Name; + BodyText = collection.Name.Value; Icon = FontAwesome.Regular.TrashAlt; diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 67756cdb43..7c1a2e1287 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -67,7 +67,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Size = Vector2.One, CornerRadius = item_height / 2, - Text = collection.Name + Current = collection.Name }, } }, diff --git a/osu.Game/Collections/ManageCollectionDialog.cs b/osu.Game/Collections/ManageCollectionDialog.cs index 6a0d815e43..1e222a9c71 100644 --- a/osu.Game/Collections/ManageCollectionDialog.cs +++ b/osu.Game/Collections/ManageCollectionDialog.cs @@ -97,7 +97,7 @@ namespace osu.Game.Collections Size = Vector2.One, Padding = new MarginPadding(10), Text = "Create new collection", - Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = "My new collection" }) + Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "My new collection" } }) }, }, } @@ -120,6 +120,9 @@ namespace osu.Game.Collections this.FadeOut(exit_duration, Easing.OutQuint); this.ScaleTo(0.9f, exit_duration); + + // Ensure that textboxes commit + GetContainingInputManager()?.TriggerFocusContention(this); } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 8fd428d26e..6c43bf5bed 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Select.Carousel private MenuItem createCollectionMenuItem(BeatmapCollection collection) { - return new ToggleMenuItem(collection.Name, MenuItemType.Standard, s => + return new ToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => { if (s) collection.Beatmaps.Add(beatmap); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 12c6b320e9..fc262730cb 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; - return new TernaryStateMenuItem(collection.Name, MenuItemType.Standard, s => + return new TernaryStateMenuItem(collection.Name.Value, MenuItemType.Standard, s => { foreach (var b in beatmapSet.Beatmaps) { diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 0db24f0738..a66bcfb3b1 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name ?? "All beatmaps"; + protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); From 33b76015d80df41618867a94227fbe2251bae200 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Sep 2020 01:54:08 +0300 Subject: [PATCH 052/137] Fix MusicActionHandler unnecessarily depending on OnScreenDisplay's existance --- osu.Game/Overlays/Music/MusicActionHandler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicActionHandler.cs b/osu.Game/Overlays/Music/MusicActionHandler.cs index 17aa4c1d32..cd8548c1c0 100644 --- a/osu.Game/Overlays/Music/MusicActionHandler.cs +++ b/osu.Game/Overlays/Music/MusicActionHandler.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Music [Resolved] private MusicController musicController { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } public bool OnPressed(GlobalAction action) @@ -34,14 +34,14 @@ namespace osu.Game.Overlays.Music { case GlobalAction.MusicPlay: if (musicController.TogglePause()) - onScreenDisplay.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); + onScreenDisplay?.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); return true; case GlobalAction.MusicNext: musicController.NextTrack(() => { - onScreenDisplay.Display(new MusicActionToast("Next track")); + onScreenDisplay?.Display(new MusicActionToast("Next track")); }).RunTask(); return true; @@ -52,11 +52,11 @@ namespace osu.Game.Overlays.Music switch (res) { case PreviousTrackResult.Restart: - onScreenDisplay.Display(new MusicActionToast("Restart track")); + onScreenDisplay?.Display(new MusicActionToast("Restart track")); break; case PreviousTrackResult.Previous: - onScreenDisplay.Display(new MusicActionToast("Previous track")); + onScreenDisplay?.Display(new MusicActionToast("Previous track")); break; } }).RunTask(); From e37a3a84fd2e232d6f1eeb0f71eec3c64417256d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Sep 2020 15:40:26 +0200 Subject: [PATCH 053/137] Use legible tuple member name --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9121309489..9ee86eaf78 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Menus public void TestMusicNavigationActions() { int importId = 0; - Queue<(WorkingBeatmap working, TrackChangeDirection dir)> trackChangeQueue = null; + Queue<(WorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo @@ -55,8 +55,8 @@ namespace osu.Game.Tests.Visual.Menus AddStep("bind to track change", () => { - trackChangeQueue = new Queue<(WorkingBeatmap working, TrackChangeDirection dir)>(); - Game.MusicController.TrackChanged += (working, dir) => trackChangeQueue.Enqueue((working, dir)); + trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>(); + Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection)); }); AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); @@ -69,12 +69,12 @@ namespace osu.Game.Tests.Visual.Menus AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("track changed to previous", () => trackChangeQueue.Count == 1 && - trackChangeQueue.Dequeue().dir == TrackChangeDirection.Prev); + trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); AddStep("trigger music next action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); AddAssert("track changed to next", () => trackChangeQueue.Count == 1 && - trackChangeQueue.Dequeue().dir == TrackChangeDirection.Next); + trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); } } } From 8b1151284c507b12cbb56811a90018f916645873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Sep 2020 15:43:16 +0200 Subject: [PATCH 054/137] Simplify overly verbose step names --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9ee86eaf78..9b8ba47992 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -18,9 +18,9 @@ namespace osu.Game.Tests.Visual.Menus public void TestMusicPlayAction() { AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); - AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused); - AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused); } @@ -62,16 +62,16 @@ namespace osu.Game.Tests.Visual.Menus AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000); - AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("no track change", () => trackChangeQueue.Count == 0); AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); - AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("track changed to previous", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); - AddStep("trigger music next action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); + AddStep("press next", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); AddAssert("track changed to next", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); From b91a376f0a9438191b285510b88aa2c6b693632b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 20:06:38 +0900 Subject: [PATCH 055/137] Split dropdown into separate file --- .../Select/CollectionFilterDropdown.cs | 188 ++++++++++++++++++ osu.Game/Screens/Select/FilterControl.cs | 172 ---------------- 2 files changed, 188 insertions(+), 172 deletions(-) create mode 100644 osu.Game/Screens/Select/CollectionFilterDropdown.cs diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs new file mode 100644 index 0000000000..883c2c69f0 --- /dev/null +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -0,0 +1,188 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Select +{ + public class CollectionFilterDropdown : OsuDropdown + { + private readonly IBindableList collections = new BindableList(); + private readonly IBindableList beatmaps = new BindableList(); + private readonly BindableList filters = new BindableList(); + + public CollectionFilterDropdown() + { + ItemSource = filters; + } + + [BackgroundDependencyLoader] + private void load(BeatmapCollectionManager collectionManager) + { + collections.BindTo(collectionManager.Collections); + collections.CollectionChanged += (_, __) => collectionsChanged(); + collectionsChanged(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(filterChanged, true); + } + + /// + /// Occurs when a collection has been added or removed. + /// + private void collectionsChanged() + { + var selectedItem = SelectedItem?.Value?.Collection; + + filters.Clear(); + filters.Add(new FilterControl.CollectionFilter(null)); + filters.AddRange(collections.Select(c => new FilterControl.CollectionFilter(c))); + + Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + } + + /// + /// Occurs when the selection has changed. + /// + private void filterChanged(ValueChangedEvent filter) + { + beatmaps.CollectionChanged -= filterBeatmapsChanged; + + if (filter.OldValue?.Collection != null) + beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); + + if (filter.NewValue?.Collection != null) + beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); + + beatmaps.CollectionChanged += filterBeatmapsChanged; + } + + /// + /// Occurs when the beatmaps contained by a have changed. + /// + private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. + // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. + Current.TriggerChange(); + } + + protected override string GenerateItemText(FilterControl.CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; + + protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + + protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); + + private class CollectionDropdownHeader : OsuDropdownHeader + { + public CollectionDropdownHeader() + { + Height = 25; + Icon.Size = new Vector2(16); + Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; + } + } + + private class CollectionDropdownMenu : OsuDropdownMenu + { + public CollectionDropdownMenu() + { + MaxHeight = 200; + } + + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); + } + + private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem + { + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + + [CanBeNull] + private readonly BindableList collectionBeatmaps; + + private IconButton addOrRemoveButton; + + public CollectionDropdownMenuItem(MenuItem item) + : base(item) + { + collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + addOrRemoveButton = new IconButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, + Scale = new Vector2(0.75f), + Alpha = collectionBeatmaps == null ? 0 : 1, + Action = addOrRemove + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (collectionBeatmaps != null) + { + collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); + beatmap.BindValueChanged(_ => collectionChanged(), true); + } + } + + private void collectionChanged() + { + Debug.Assert(collectionBeatmaps != null); + + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + + if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) + { + addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.IconColour = colours.Red; + } + else + { + addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.IconColour = colours.Green; + } + } + + private void addOrRemove() + { + Debug.Assert(collectionBeatmaps != null); + + if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) + collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); + } + } + } +} diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a66bcfb3b1..706909e71e 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -11,14 +9,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -204,173 +199,6 @@ namespace osu.Game.Screens.Select updateCriteria(); } - private class CollectionFilterDropdown : OsuDropdown - { - private readonly IBindableList collections = new BindableList(); - private readonly IBindableList beatmaps = new BindableList(); - private readonly BindableList filters = new BindableList(); - - public CollectionFilterDropdown() - { - ItemSource = filters; - } - - [BackgroundDependencyLoader] - private void load(BeatmapCollectionManager collectionManager) - { - collections.BindTo(collectionManager.Collections); - collections.CollectionChanged += (_, __) => collectionsChanged(); - collectionsChanged(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindValueChanged(filterChanged, true); - } - - /// - /// Occurs when a collection has been added or removed. - /// - private void collectionsChanged() - { - var selectedItem = SelectedItem?.Value?.Collection; - - filters.Clear(); - filters.Add(new CollectionFilter(null)); - filters.AddRange(collections.Select(c => new CollectionFilter(c))); - - Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; - } - - /// - /// Occurs when the selection has changed. - /// - private void filterChanged(ValueChangedEvent filter) - { - beatmaps.CollectionChanged -= filterBeatmapsChanged; - - if (filter.OldValue?.Collection != null) - beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); - - if (filter.NewValue?.Collection != null) - beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); - - beatmaps.CollectionChanged += filterBeatmapsChanged; - } - - /// - /// Occurs when the beatmaps contained by a have changed. - /// - private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. - // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. - Current.TriggerChange(); - } - - protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; - - protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); - - protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); - - private class CollectionDropdownHeader : OsuDropdownHeader - { - public CollectionDropdownHeader() - { - Height = 25; - Icon.Size = new Vector2(16); - Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; - } - } - - private class CollectionDropdownMenu : OsuDropdownMenu - { - public CollectionDropdownMenu() - { - MaxHeight = 200; - } - - protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); - } - - private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem - { - [Resolved] - private OsuColour colours { get; set; } - - [Resolved] - private IBindable beatmap { get; set; } - - [CanBeNull] - private readonly BindableList collectionBeatmaps; - - private IconButton addOrRemoveButton; - - public CollectionDropdownMenuItem(MenuItem item) - : base(item) - { - collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); - } - - [BackgroundDependencyLoader] - private void load() - { - AddRangeInternal(new Drawable[] - { - addOrRemoveButton = new IconButton - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.75f), - Alpha = collectionBeatmaps == null ? 0 : 1, - Action = addOrRemove - } - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (collectionBeatmaps != null) - { - collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); - beatmap.BindValueChanged(_ => collectionChanged(), true); - } - } - - private void collectionChanged() - { - Debug.Assert(collectionBeatmaps != null); - - addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; - - if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) - { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; - addOrRemoveButton.IconColour = colours.Red; - } - else - { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; - addOrRemoveButton.IconColour = colours.Green; - } - } - - private void addOrRemove() - { - Debug.Assert(collectionBeatmaps != null); - - if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) - collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); - } - } - } - public class CollectionFilter { [CanBeNull] From 120dfd50a6c6ddb4f74f8cb63dd81c9026920672 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 20:29:28 +0900 Subject: [PATCH 056/137] Fix collection names not updating in dropdown --- .../Select/CollectionFilterDropdown.cs | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 883c2c69f0..6b5d63771f 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -87,18 +87,47 @@ namespace osu.Game.Screens.Select protected override string GenerateItemText(FilterControl.CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; - protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader + { + SelectedItem = { BindTarget = Current } + }; protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); private class CollectionDropdownHeader : OsuDropdownHeader { + public readonly Bindable SelectedItem = new Bindable(); + private readonly Bindable collectionName = new Bindable(); + + protected override string Label + { + get => base.Label; + set { } // See updateText(). + } + public CollectionDropdownHeader() { Height = 25; Icon.Size = new Vector2(16); Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedItem.BindValueChanged(_ => updateBindable(), true); + } + + private void updateBindable() + { + collectionName.UnbindAll(); + collectionName.BindTo(SelectedItem.Value.Collection?.Name ?? new Bindable("All beatmaps")); + collectionName.BindValueChanged(_ => updateText(), true); + } + + // Dropdowns don't bind to value changes, so the real name is copied directly from the selected item here. + private void updateText() => base.Label = collectionName.Value; } private class CollectionDropdownMenu : OsuDropdownMenu @@ -113,6 +142,9 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { + [NotNull] + protected new FilterControl.CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; + [Resolved] private OsuColour colours { get; set; } @@ -122,12 +154,17 @@ namespace osu.Game.Screens.Select [CanBeNull] private readonly BindableList collectionBeatmaps; + [NotNull] + private readonly Bindable collectionName; + private IconButton addOrRemoveButton; + private Content content; public CollectionDropdownMenuItem(MenuItem item) : base(item) { - collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); + collectionBeatmaps = Item.Collection?.Beatmaps.GetBoundCopy(); + collectionName = Item.Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } [BackgroundDependencyLoader] @@ -156,6 +193,10 @@ namespace osu.Game.Screens.Select collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); beatmap.BindValueChanged(_ => collectionChanged(), true); } + + // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge + // of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed. + collectionName.BindValueChanged(name => content.Text = name.NewValue, true); } private void collectionChanged() @@ -183,6 +224,8 @@ namespace osu.Game.Screens.Select if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); } + + protected override Drawable CreateContent() => content = (Content)base.CreateContent(); } } } From c1d255a04c74c05949bfbe3d9b3ec784a4834047 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 20:44:39 +0900 Subject: [PATCH 057/137] Split filter control into separate class --- osu.Game/Screens/Select/CollectionFilter.cs | 24 +++++++++++++++++++ .../Select/CollectionFilterDropdown.cs | 18 +++++++------- osu.Game/Screens/Select/FilterControl.cs | 18 -------------- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- 4 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Screens/Select/CollectionFilter.cs diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs new file mode 100644 index 0000000000..e1f19b41c3 --- /dev/null +++ b/osu.Game/Screens/Select/CollectionFilter.cs @@ -0,0 +1,24 @@ +// 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 JetBrains.Annotations; +using osu.Game.Beatmaps; +using osu.Game.Collections; + +namespace osu.Game.Screens.Select +{ + public class CollectionFilter + { + [CanBeNull] + public readonly BeatmapCollection Collection; + + public CollectionFilter([CanBeNull] BeatmapCollection collection) + { + Collection = collection; + } + + public virtual bool ContainsBeatmap(BeatmapInfo beatmap) + => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; + } +} diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 6b5d63771f..ae2f09e11a 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -19,11 +19,11 @@ using osuTK; namespace osu.Game.Screens.Select { - public class CollectionFilterDropdown : OsuDropdown + public class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); - private readonly BindableList filters = new BindableList(); + private readonly BindableList filters = new BindableList(); public CollectionFilterDropdown() { @@ -53,16 +53,16 @@ namespace osu.Game.Screens.Select var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); - filters.Add(new FilterControl.CollectionFilter(null)); - filters.AddRange(collections.Select(c => new FilterControl.CollectionFilter(c))); + filters.Add(new CollectionFilter(null)); + filters.AddRange(collections.Select(c => new CollectionFilter(c))); Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; } /// - /// Occurs when the selection has changed. + /// Occurs when the selection has changed. /// - private void filterChanged(ValueChangedEvent filter) + private void filterChanged(ValueChangedEvent filter) { beatmaps.CollectionChanged -= filterBeatmapsChanged; @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(FilterControl.CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; + protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader { @@ -96,7 +96,7 @@ namespace osu.Game.Screens.Select private class CollectionDropdownHeader : OsuDropdownHeader { - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); protected override string Label @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { [NotNull] - protected new FilterControl.CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; + protected new CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 706909e71e..41ce0d65cd 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; -using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -199,20 +195,6 @@ namespace osu.Game.Screens.Select updateCriteria(); } - public class CollectionFilter - { - [CanBeNull] - public readonly BeatmapCollection Collection; - - public CollectionFilter([CanBeNull] BeatmapCollection collection) - { - Collection = collection; - } - - public virtual bool ContainsBeatmap(BeatmapInfo beatmap) - => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; - } - public void Deactivate() { searchTextBox.ReadOnly = true; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 5a5c0e1b50..af4802f308 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select } } - public FilterControl.CollectionFilter Collection; + public CollectionFilter Collection; public struct OptionalRange : IEquatable> where T : struct From 98e9c4dc256e3397d3ed2fa15c269bc7f0239943 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 21:08:48 +0900 Subject: [PATCH 058/137] General refactorings --- .../Collections/TestSceneCollectionDialog.cs | 2 +- osu.Game/Collections/BeatmapCollection.cs | 47 +++++++++++++++++++ .../Collections/BeatmapCollectionManager.cs | 27 +---------- ...onDialog.cs => ManageCollectionsDialog.cs} | 4 +- osu.Game/OsuGame.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 8 ++-- .../Carousel/DrawableCarouselBeatmapSet.cs | 8 ++-- osu.Game/Screens/Select/CollectionFilter.cs | 24 ++++++++++ .../Select/CollectionFilterDropdown.cs | 8 +++- osu.Game/Screens/Select/FilterCriteria.cs | 5 ++ 10 files changed, 95 insertions(+), 40 deletions(-) create mode 100644 osu.Game/Collections/BeatmapCollection.cs rename osu.Game/Collections/{ManageCollectionDialog.cs => ManageCollectionsDialog.cs} (97%) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs index 247d27f67a..5782e627ba 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Collections { Children = new Drawable[] { - new ManageCollectionDialog { State = { Value = Visibility.Visible } }, + new ManageCollectionsDialog { State = { Value = Visibility.Visible } }, dialogOverlay = new DialogOverlay() }; } diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs new file mode 100644 index 0000000000..7e4b15ecf9 --- /dev/null +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -0,0 +1,47 @@ +// 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.Game.Beatmaps; + +namespace osu.Game.Collections +{ + /// + /// A collection of beatmaps grouped by a name. + /// + public class BeatmapCollection + { + /// + /// Invoked whenever any change occurs on this . + /// + public event Action Changed; + + /// + /// The collection's name. + /// + public readonly Bindable Name = new Bindable(); + + /// + /// The beatmaps contained by the collection. + /// + public readonly BindableList Beatmaps = new BindableList(); + + /// + /// The date when this collection was last modified. + /// + public DateTimeOffset LastModifyDate { get; private set; } = DateTimeOffset.UtcNow; + + public BeatmapCollection() + { + Beatmaps.CollectionChanged += (_, __) => onChange(); + Name.ValueChanged += _ => onChange(); + } + + private void onChange() + { + LastModifyDate = DateTimeOffset.Now; + Changed?.Invoke(); + } + } +} diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 3e5976300f..ed07f0d3e2 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -21,7 +21,7 @@ namespace osu.Game.Collections public class BeatmapCollectionManager : CompositeDrawable { /// - /// Database version in YYYYMMDD format (matching stable). + /// Database version in stable-compatible YYYYMMDD format. /// private const int database_version = 30000000; @@ -213,29 +213,4 @@ namespace osu.Game.Collections save(); } } - - public class BeatmapCollection - { - /// - /// Invoked whenever any change occurs on this . - /// - public event Action Changed; - - public readonly Bindable Name = new Bindable(); - - public readonly BindableList Beatmaps = new BindableList(); - - public DateTimeOffset LastModifyTime { get; private set; } - - public BeatmapCollection() - { - LastModifyTime = DateTimeOffset.UtcNow; - - Beatmaps.CollectionChanged += (_, __) => - { - LastModifyTime = DateTimeOffset.Now; - Changed?.Invoke(); - }; - } - } } diff --git a/osu.Game/Collections/ManageCollectionDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs similarity index 97% rename from osu.Game/Collections/ManageCollectionDialog.cs rename to osu.Game/Collections/ManageCollectionsDialog.cs index 1e222a9c71..f2aedb1c29 100644 --- a/osu.Game/Collections/ManageCollectionDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Collections { - public class ManageCollectionDialog : OsuFocusedOverlayContainer + public class ManageCollectionsDialog : OsuFocusedOverlayContainer { private const double enter_duration = 500; private const double exit_duration = 200; @@ -21,7 +21,7 @@ namespace osu.Game.Collections [Resolved] private BeatmapCollectionManager collectionManager { get; set; } - public ManageCollectionDialog() + public ManageCollectionsDialog() { Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 701a65dbeb..8434ee11fa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -618,7 +618,7 @@ namespace osu.Game loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements - loadComponentSingleFile(new ManageCollectionDialog(), overlayContent.Add, true); + loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 6c43bf5bed..008cf85018 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private ManageCollectionDialog manageCollectionDialog { get; set; } + private ManageCollectionsDialog manageCollectionsDialog { get; set; } private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -227,9 +227,9 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (manageCollectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fc262730cb..fe0ad31b32 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private ManageCollectionDialog manageCollectionDialog { get; set; } + private ManageCollectionsDialog manageCollectionsDialog { get; set; } private readonly BeatmapSetInfo beatmapSet; @@ -148,9 +148,9 @@ namespace osu.Game.Screens.Select.Carousel if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (manageCollectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs index e1f19b41c3..7628ed391e 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionFilter.cs @@ -3,21 +3,45 @@ using System.Linq; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Collections; namespace osu.Game.Screens.Select { + /// + /// A filter. + /// public class CollectionFilter { + /// + /// The collection to filter beatmaps from. + /// May be null to not filter by collection (include all beatmaps). + /// [CanBeNull] public readonly BeatmapCollection Collection; + /// + /// The name of the collection. + /// + [NotNull] + public readonly Bindable CollectionName; + + /// + /// Creates a new . + /// + /// The collection to filter beatmaps from. public CollectionFilter([CanBeNull] BeatmapCollection collection) { Collection = collection; + CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } + /// + /// Whether the collection contains a given beatmap. + /// + /// The beatmap to check. + /// Whether contains . public virtual bool ContainsBeatmap(BeatmapInfo beatmap) => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index ae2f09e11a..02484f6c64 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -19,6 +19,9 @@ using osuTK; namespace osu.Game.Screens.Select { + /// + /// A dropdown to select the to filter beatmaps using. + /// public class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); @@ -64,6 +67,7 @@ namespace osu.Game.Screens.Select /// private void filterChanged(ValueChangedEvent filter) { + // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. beatmaps.CollectionChanged -= filterBeatmapsChanged; if (filter.OldValue?.Collection != null) @@ -122,7 +126,7 @@ namespace osu.Game.Screens.Select private void updateBindable() { collectionName.UnbindAll(); - collectionName.BindTo(SelectedItem.Value.Collection?.Name ?? new Bindable("All beatmaps")); + collectionName.BindTo(SelectedItem.Value.CollectionName); collectionName.BindValueChanged(_ => updateText(), true); } @@ -164,7 +168,7 @@ namespace osu.Game.Screens.Select : base(item) { collectionBeatmaps = Item.Collection?.Beatmaps.GetBoundCopy(); - collectionName = Item.Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); + collectionName = Item.CollectionName.GetBoundCopy(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index af4802f308..acab982945 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -51,6 +52,10 @@ namespace osu.Game.Screens.Select } } + /// + /// The collection to filter beatmaps from. + /// + [CanBeNull] public CollectionFilter Collection; public struct OptionalRange : IEquatable> From ad625ecc7a199e3f93a4e827312d07b4fbeee957 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 22:10:12 +0900 Subject: [PATCH 059/137] Add collection IO tests --- .../Collections/IO/ImportCollectionsTest.cs | 215 ++++++++++++++++++ .../Resources/Collections/collections.db | Bin 0 -> 473 bytes .../Collections/BeatmapCollectionManager.cs | 22 +- 3 files changed, 232 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs create mode 100644 osu.Game.Tests/Resources/Collections/collections.db diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs new file mode 100644 index 0000000000..7d772d3989 --- /dev/null +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -0,0 +1,215 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Collections.IO +{ + [TestFixture] + public class ImportCollectionsTest + { + [Test] + public async Task TestImportEmptyDatabase() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportEmptyDatabase")) + { + try + { + var osu = await loadOsu(host); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(new MemoryStream()); + + Assert.That(collectionManager.Collections.Count, Is.Zero); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportWithNoBeatmaps() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithNoBeatmaps")) + { + try + { + var osu = await loadOsu(host); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + + Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.Zero); + + Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.Zero); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportWithBeatmaps() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithBeatmaps")) + { + try + { + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + + Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1)); + + Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportMalformedDatabase() + { + bool exceptionThrown = false; + UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true; + + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMalformedDatabase")) + { + try + { + AppDomain.CurrentDomain.UnhandledException += setException; + + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + + using (var ms = new MemoryStream()) + { + using (var bw = new BinaryWriter(ms, Encoding.UTF8, true)) + { + for (int i = 0; i < 10000; i++) + bw.Write((byte)i); + } + + ms.Seek(0, SeekOrigin.Begin); + + await collectionManager.Import(ms); + } + + Assert.That(host.UpdateThread.Running, Is.True); + Assert.That(exceptionThrown, Is.False); + Assert.That(collectionManager.Collections.Count, Is.EqualTo(0)); + } + finally + { + host.Exit(); + AppDomain.CurrentDomain.UnhandledException -= setException; + } + } + } + + [Test] + public async Task TestSaveAndReload() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestSaveAndReload")) + { + try + { + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + + // Move first beatmap from second collection into the first. + collectionManager.Collections[0].Beatmaps.Add(collectionManager.Collections[1].Beatmaps[0]); + collectionManager.Collections[1].Beatmaps.RemoveAt(0); + + // Rename the second collecction. + collectionManager.Collections[1].Name.Value = "Another"; + } + finally + { + host.Exit(); + } + } + + using (HeadlessGameHost host = new HeadlessGameHost("TestSaveAndReload")) + { + try + { + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + + Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); + Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11)); + } + finally + { + host.Exit(); + } + } + } + + private async Task loadOsu(GameHost host, bool withBeatmap = false) + { + var osu = new OsuGameBase(); + +#pragma warning disable 4014 + Task.Run(() => host.Run(osu)); +#pragma warning restore 4014 + + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + if (withBeatmap) + { + var beatmapFile = TestResources.GetTestBeatmapForImport(); + var beatmapManager = osu.Dependencies.Get(); + await beatmapManager.Import(beatmapFile); + } + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tests/Resources/Collections/collections.db b/osu.Game.Tests/Resources/Collections/collections.db new file mode 100644 index 0000000000000000000000000000000000000000..83e1c0f10a9058f3f376e75d3b5a88f671a95f63 GIT binary patch literal 473 zcmah_%W0lL4E!CiFJFrIO3>=Ld+;?4qyjxw;7bCryL3}or-29rgV2mL^ZCk8-yV<0 z_59=Q&-=&I7rdJX!-hP)U7D7xkTeI>lFo6x{M`BbSAGAty`>hfB!!t?h{`*ueUU(z zwqLg>Kq9oOjOI0BLY+xkyAg0+_rhpwBocF}ptLdFxMa3XucLvnYP9pNF;*O~cDSX^ zm8A@4W6w#hixgKP0&(j^RSP^kU2@%VUNsbpr9-6>Ab1NHf^gOz*PS64xg(D-ELaUW z$6;(dB@EZi9CA)@46-aEqHY}+Vltepue&O_nhD)wo)}crPm*6o_5?jw{+sW8lH + return Task.Run(async () => { var storage = GetStableStorage(); if (storage.Exists(database_name)) { using (var stream = storage.GetStream(database_name)) - { - var collection = readCollections(stream); - Schedule(() => importCollections(collection)); - } + await Import(stream); } }); } + public async Task Import(Stream stream) => await Task.Run(async () => + { + var collection = readCollections(stream); + bool importCompleted = false; + + Schedule(() => + { + importCollections(collection); + importCompleted = true; + }); + + while (!IsDisposed && !importCompleted) + await Task.Delay(10); + }); + private void importCollections(List newCollections) { foreach (var newCol in newCollections) From 0d5d293279eb96bcefc739c19fd69da4fcdcdad2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 22:47:19 +0900 Subject: [PATCH 060/137] Add manage collections dialog tests --- .../Collections/TestSceneCollectionDialog.cs | 26 --- .../TestSceneManageCollectionsDialog.cs | 198 ++++++++++++++++++ .../Collections/BeatmapCollectionManager.cs | 19 +- .../Collections/DrawableCollectionListItem.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 5 files changed, 212 insertions(+), 35 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs create mode 100644 osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs deleted file mode 100644 index 5782e627ba..0000000000 --- a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Graphics.Containers; -using osu.Game.Collections; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual.Collections -{ - public class TestSceneCollectionDialog : OsuTestScene - { - [Cached] - private DialogOverlay dialogOverlay; - - public TestSceneCollectionDialog() - { - Children = new Drawable[] - { - new ManageCollectionsDialog { State = { Value = Visibility.Visible } }, - dialogOverlay = new DialogOverlay() - }; - } - } -} diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs new file mode 100644 index 0000000000..2d6f8abd8b --- /dev/null +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -0,0 +1,198 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; +using osu.Game.Collections; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Collections +{ + public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene + { + [Cached] + private readonly DialogOverlay dialogOverlay; + + protected override Container Content => content; + + private readonly Container content; + + private BeatmapCollectionManager manager; + private ManageCollectionsDialog dialog; + + public TestSceneManageCollectionsDialog() + { + base.Content.AddRange(new Drawable[] + { + content = new Container { RelativeSizeAxes = Axes.Both }, + dialogOverlay = new DialogOverlay() + }); + } + + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(manager = new BeatmapCollectionManager(LocalStorage)); + Add(manager); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + manager.Collections.Clear(); + Child = dialog = new ManageCollectionsDialog(); + }); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("show dialog", () => dialog.Show()); + } + + [Test] + public void TestHideDialog() + { + AddWaitStep("wait for animation", 3); + AddStep("hide dialog", () => dialog.Hide()); + } + + [Test] + public void TestAddCollectionExternal() + { + AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } })); + assertCollectionCount(1); + assertCollectionName(0, "First collection"); + + AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } })); + assertCollectionCount(2); + assertCollectionName(1, "Second collection"); + } + + [Test] + public void TestAddCollectionViaButton() + { + AddStep("press new collection button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(1); + + AddStep("press again", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(2); + } + + [Test] + public void TestRemoveCollectionExternal() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("remove first collection", () => manager.Collections.RemoveAt(0)); + assertCollectionCount(1); + assertCollectionName(0, "2"); + } + + [Test] + public void TestRemoveCollectionViaButton() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("click first delete button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); + AddStep("click confirmation", () => + { + InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(1); + assertCollectionName(0, "2"); + } + + [Test] + public void TestCollectionNotRemovedWhenDialogCancelled() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("click first delete button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); + AddStep("click confirmation", () => + { + InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(2); + } + + [Test] + public void TestCollectionRenamedExternal() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First"); + + assertCollectionName(0, "First"); + } + + [Test] + public void TestCollectionRenamedOnTextChange() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); + AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First"); + } + + private void assertCollectionCount(int count) + => AddAssert($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); + + private void assertCollectionName(int index, string name) + => AddAssert($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); + } +} diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 3d3e9e0e07..a553ac632e 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -37,12 +37,19 @@ namespace osu.Game.Collections [Resolved] private BeatmapManager beatmaps { get; set; } + private readonly Storage storage; + + public BeatmapCollectionManager(Storage storage) + { + this.storage = storage; + } + [BackgroundDependencyLoader] private void load() { - if (host.Storage.Exists(database_name)) + if (storage.Exists(database_name)) { - using (var stream = host.Storage.GetStream(database_name)) + using (var stream = storage.GetStream(database_name)) importCollections(readCollections(stream)); } @@ -78,11 +85,9 @@ namespace osu.Game.Collections return Task.Run(async () => { - var storage = GetStableStorage(); - - if (storage.Exists(database_name)) + if (stable.Exists(database_name)) { - using (var stream = storage.GetStream(database_name)) + using (var stream = stable.GetStream(database_name)) await Import(stream); } }); @@ -188,7 +193,7 @@ namespace osu.Game.Collections { // This is NOT thread-safe!! - using (var sw = new SerializationWriter(host.Storage.GetStream(database_name, FileAccess.Write))) + using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write))) { sw.Write(database_version); sw.Write(Collections.Count); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 7c1a2e1287..c7abf58d10 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -87,7 +87,7 @@ namespace osu.Game.Collections } } - private class DeleteButton : CompositeDrawable + public class DeleteButton : CompositeDrawable { public Func IsTextBoxHovered; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 30494d18fb..3114727d54 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -228,7 +228,7 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new BeatmapCollectionManager()); + dependencies.Cache(CollectionManager = new BeatmapCollectionManager(Storage)); AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); From a1214512bc96532f984166b20b77ecaa56bd890d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 23:57:49 +0900 Subject: [PATCH 061/137] Add filter control tests --- .../SongSelect/TestSceneFilterControl.cs | 213 +++++++++++++++++- .../Select/CollectionFilterDropdown.cs | 28 +-- 2 files changed, 225 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index f89300661c..fe1c194c5b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -1,22 +1,231 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; +using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { - public class TestSceneFilterControl : OsuTestScene + public class TestSceneFilterControl : OsuManualInputManagerTestScene { + protected override Container Content => content; + private readonly Container content; + + [Cached] + private readonly BeatmapCollectionManager collectionManager; + + private RulesetStore rulesets; + private BeatmapManager beatmapManager; + + private FilterControl control; + public TestSceneFilterControl() { - Child = new FilterControl + base.Content.AddRange(new Drawable[] + { + collectionManager = new BeatmapCollectionManager(LocalStorage), + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Cache(collectionManager); + return dependencies; + } + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + collectionManager.Collections.Clear(); + + Child = control = new FilterControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = FilterControl.HEIGHT, }; + }); + + [Test] + public void TestEmptyCollectionFilterContainsAllBeatmaps() + { + assertCollectionDropdownContains("All beatmaps"); + assertCollectionHeaderDisplays("All beatmaps"); } + + [Test] + public void TestCollectionAddedToDropdown() + { + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); + assertCollectionDropdownContains("1"); + assertCollectionDropdownContains("2"); + } + + [Test] + public void TestCollectionRemovedFromDropdown() + { + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); + AddStep("remove collection", () => collectionManager.Collections.RemoveAt(0)); + + assertCollectionDropdownContains("1", false); + assertCollectionDropdownContains("2"); + } + + [Test] + public void TestCollectionRenamed() + { + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("select collection", () => + { + var dropdown = control.ChildrenOfType().Single(); + dropdown.Current.Value = dropdown.ItemSource.ElementAt(1); + }); + + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First"); + + assertCollectionDropdownContains("First"); + assertCollectionHeaderDisplays("First"); + } + + [Test] + public void TestAllBeatmapFilterDoesNotHaveAddButton() + { + AddAssert("'All beatmaps' filter does not have add button", () => !getCollectionDropdownItems().First().ChildrenOfType().Single().IsPresent); + } + + [Test] + public void TestCollectionFilterHasAddButton() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("collection has add button", () => !getAddOrRemoveButton(0).IsPresent); + } + + [Test] + public void TestButtonDisabledAndEnabledWithBeatmapChanges() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value); + + AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); + AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); + + AddStep("set dummy beatmap", () => Beatmap.SetDefault()); + AddAssert("button enabled", () => !getAddOrRemoveButton(1).Enabled.Value); + } + + [Test] + public void TestButtonChangesWhenAddedAndRemovedFromCollection() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + + AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + + AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + } + + [Test] + public void TestButtonAddsAndRemovesBeatmap() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + + addClickAddOrRemoveButtonStep(1); + AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + + addClickAddOrRemoveButtonStep(1); + AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + } + + private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) + => AddAssert($"collection dropdown header displays '{collectionName}'", + () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); + + private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => + AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", + // A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872 + () => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType().OfType().First().Text == collectionName))); + + private IconButton getAddOrRemoveButton(int index) + => getCollectionDropdownItems().ElementAt(index).ChildrenOfType().Single(); + + private void addClickAddOrRemoveButtonStep(int index) + { + AddStep("click add or remove button", () => + { + InputManager.MoveMouseTo(getAddOrRemoveButton(index)); + InputManager.Click(MouseButton.Left); + }); + } + + private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() + => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 02484f6c64..2d30263d78 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Select protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); - private class CollectionDropdownHeader : OsuDropdownHeader + public class CollectionDropdownHeader : OsuDropdownHeader { public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); @@ -126,7 +126,10 @@ namespace osu.Game.Screens.Select private void updateBindable() { collectionName.UnbindAll(); - collectionName.BindTo(SelectedItem.Value.CollectionName); + + if (SelectedItem.Value != null) + collectionName.BindTo(SelectedItem.Value.CollectionName); + collectionName.BindValueChanged(_ => updateText(), true); } @@ -174,17 +177,14 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] + AddInternal(addOrRemoveButton = new IconButton { - addOrRemoveButton = new IconButton - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.75f), - Alpha = collectionBeatmaps == null ? 0 : 1, - Action = addOrRemove - } + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, + Scale = new Vector2(0.75f), + Alpha = collectionBeatmaps == null ? 0 : 1, + Action = addOrRemove }); } @@ -211,12 +211,12 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.Icon = FontAwesome.Regular.MinusSquare; addOrRemoveButton.IconColour = colours.Red; } else { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.Icon = FontAwesome.Regular.PlusSquare; addOrRemoveButton.IconColour = colours.Green; } } From e37c04cb6d97c96df6d2858d4d8f31152ac3b1bc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 00:04:03 +0900 Subject: [PATCH 062/137] Change back to solid icon --- .../Visual/SongSelect/TestSceneFilterControl.cs | 12 ++++++------ osu.Game/Screens/Select/CollectionFilterDropdown.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index fe1c194c5b..89a9536a04 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -172,13 +172,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } [Test] @@ -193,15 +193,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 2d30263d78..e2e8fbe0ea 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -211,12 +211,12 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { - addOrRemoveButton.Icon = FontAwesome.Regular.MinusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; addOrRemoveButton.IconColour = colours.Red; } else { - addOrRemoveButton.Icon = FontAwesome.Regular.PlusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; addOrRemoveButton.IconColour = colours.Green; } } From ca4423af74bd57088baa5b96abdf66d0fd6fc5ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 00:07:12 +0900 Subject: [PATCH 063/137] Fix tests --- .../Visual/SongSelect/TestSceneFilterControl.cs | 15 +++++++-------- .../Screens/Select/CollectionFilterDropdown.cs | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 89a9536a04..c2dd652b3a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -151,13 +151,12 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); AddStep("set dummy beatmap", () => Beatmap.SetDefault()); - AddAssert("button enabled", () => !getAddOrRemoveButton(1).Enabled.Value); + AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value); } [Test] @@ -172,13 +171,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); } [Test] @@ -193,15 +192,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); addClickAddOrRemoveButtonStep(1); AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); addClickAddOrRemoveButtonStep(1); AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index e2e8fbe0ea..18caae9545 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -211,12 +211,12 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.MinusCircle; addOrRemoveButton.IconColour = colours.Red; } else { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.PlusCircle; addOrRemoveButton.IconColour = colours.Green; } } From 01c0b61b203df181285cc052fb73509073ffefbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 01:52:31 +0900 Subject: [PATCH 064/137] Fix incorrect test names --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cef8105490..0702b02bb1 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -761,7 +761,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewEmptyBeatmap() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewEmptyBeatmap))) { try { @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewBeatmapWithObject() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewBeatmapWithObject))) { try { From 5268eee0fb7a51866d78eec067a4d5e2e069f264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 11:31:42 +0900 Subject: [PATCH 065/137] Avoid requiring sending the calling method for CleanRunHeadlessGameHost --- .../Beatmaps/IO/ImportBeatmapTest.cs | 38 +++++++++---------- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 10 ++--- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 12 +++++- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0702b02bb1..dd3dba1274 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWhenClosed() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDelete() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithReZip() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithChangedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithDifferentFilename() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportCorruptThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -301,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestRollbackOnFailure() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -378,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -406,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString())) { try { @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWithDuplicateBeatmapIDs() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -526,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWhenFileOpen() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -548,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithDuplicateHashes() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -590,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportNestedStructure() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -635,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithIgnoredDirectoryInArchive() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -689,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapInfo() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapInfo))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -719,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -761,7 +761,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewEmptyBeatmap() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewEmptyBeatmap))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewBeatmapWithObject() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewBeatmapWithObject))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 57f0d7e957..a4d20714fa 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestBasicImport() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestImportMods() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestImportStatistics() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestImportWithDeletedBeatmapSet() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestOnlineScoreIsAvailableLocally() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index bfbf7bb9da..baa7b27d28 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Runtime.CompilerServices; using osu.Framework.Platform; namespace osu.Game.Tests @@ -10,8 +11,15 @@ namespace osu.Game.Tests /// public class CleanRunHeadlessGameHost : HeadlessGameHost { - public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true) - : base(gameName, bindIPC, realtime) + /// + /// Create a new instance. + /// + /// An optional suffix which will isolate this host from others called from the same method source. + /// Whether to bind IPC channels. + /// Whether the host should be forced to run in realtime, rather than accelerated test time. + /// The name of the calling method, used for test file isolation and clean-up. + public CleanRunHeadlessGameHost(string gameSuffix = @"", bool bindIPC = false, bool realtime = true, [CallerMemberName] string callingMethodName = @"") + : base(callingMethodName + gameSuffix, bindIPC, realtime) { } From 3e5ea6c42fc43c6a31635863481cb0186e799952 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 11:59:47 +0900 Subject: [PATCH 066/137] Change "Add to" to "Collections" Doesn't make send to be 'add to' when it can also remove --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 008cf85018..5618f8f97f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Select.Carousel if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); return items.ToArray(); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fe0ad31b32..78ec59eb5b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Carousel if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); return items.ToArray(); } From b15bbc882af8150af0681dac588a18afe17bc61a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:04:35 +0900 Subject: [PATCH 067/137] Move items up in menu --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 6 +++--- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5618f8f97f..28c3529fc0 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -221,9 +221,6 @@ namespace osu.Game.Screens.Select.Carousel if (editRequested != null) items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap))); - if (hideRequested != null) - items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); - if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); @@ -233,6 +230,9 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + if (hideRequested != null) + items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); + return items.ToArray(); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 78ec59eb5b..327cbc4765 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -142,18 +142,17 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); - if (beatmapSet.Beatmaps.Any(b => b.Hidden)) - items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); - - if (dialogOverlay != null) - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); + + if (dialogOverlay != null) + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); return items.ToArray(); } } From 8b770626fa877e08eef3baf54aa3657c84b4b664 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:18:08 +0900 Subject: [PATCH 068/137] Add missing '...' from some popup menu items --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 28c3529fc0..9f21ec1ad1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap))); if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) - items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 327cbc4765..19ecc277c4 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); if (dialogOverlay != null) - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); + items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); return items.ToArray(); } } From ab58f60529d5a53cda28ee8cc010ac3f13d23cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:47:21 +0900 Subject: [PATCH 069/137] Remove elasticity from dialog appearing --- osu.Game/Collections/ManageCollectionsDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index f2aedb1c29..036a745913 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -111,7 +111,7 @@ namespace osu.Game.Collections base.PopIn(); this.FadeIn(enter_duration, Easing.OutQuint); - this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutElastic); + this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); } protected override void PopOut() From 3e96c6d036116cd8ef477dd529a26ccc90f90f6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:51:42 +0900 Subject: [PATCH 070/137] Improve paddings of collection management dialog --- osu.Game/Collections/DrawableCollectionList.cs | 3 ++- osu.Game/Collections/DrawableCollectionListItem.cs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index ab146c17b6..e8bde9066f 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -13,13 +13,14 @@ namespace osu.Game.Collections protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { d.ScrollbarVisible = false; + d.Padding = new MarginPadding(10); }); protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> { LayoutDuration = 200, LayoutEasing = Easing.OutQuint, - Spacing = new Vector2(0, 2) + Spacing = new Vector2(0, 5) }; protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new DrawableCollectionListItem(item); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index c7abf58d10..e11f14ccae 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -25,7 +25,6 @@ namespace osu.Game.Collections public DrawableCollectionListItem(BeatmapCollection item) : base(item) { - Padding = new MarginPadding { Right = 20 }; } protected override Drawable CreateContent() => new ItemContent(Model); From 0e93bbb62df3c7197513b414e2612559f0f5e2f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 13:02:58 +0900 Subject: [PATCH 071/137] Adjust sizing of delete button --- osu.Game/Collections/DrawableCollectionListItem.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index e11f14ccae..9b7505f7c3 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -22,6 +22,8 @@ namespace osu.Game.Collections { private const float item_height = 35; + private const float button_width = item_height * 0.75f; + public DrawableCollectionListItem(BeatmapCollection item) : base(item) { @@ -58,7 +60,7 @@ namespace osu.Game.Collections new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = item_height / 2 }, + Padding = new MarginPadding { Right = button_width }, Children = new Drawable[] { textBox = new ItemTextBox @@ -100,8 +102,9 @@ namespace osu.Game.Collections public DeleteButton(BeatmapCollection collection) { this.collection = collection; - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; + RelativeSizeAxes = Axes.Y; + + Width = button_width + item_height / 2; // add corner radius to cover with fill Alpha = 0.1f; } @@ -119,8 +122,8 @@ namespace osu.Game.Collections new SpriteIcon { Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - X = -6, + Origin = Anchor.Centre, + X = -button_width * 0.6f, Size = new Vector2(10), Icon = FontAwesome.Solid.Trash } From 525026e7f09d788f5abdce40b546b0c7617569d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 13:23:50 +0900 Subject: [PATCH 072/137] Fix tests failing due to timings --- .../TestSceneManageCollectionsDialog.cs | 26 ++++++++++++------- .../SongSelect/TestSceneFilterControl.cs | 1 - 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 2d6f8abd8b..fdaded6a5c 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -19,30 +19,30 @@ namespace osu.Game.Tests.Visual.Collections { public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene { - [Cached] - private readonly DialogOverlay dialogOverlay; - protected override Container Content => content; private readonly Container content; + private readonly DialogOverlay dialogOverlay; + private readonly BeatmapCollectionManager manager; - private BeatmapCollectionManager manager; private ManageCollectionsDialog dialog; public TestSceneManageCollectionsDialog() { base.Content.AddRange(new Drawable[] { + manager = new BeatmapCollectionManager(LocalStorage), content = new Container { RelativeSizeAxes = Axes.Both }, dialogOverlay = new DialogOverlay() }); } - [BackgroundDependencyLoader] - private void load() + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - Dependencies.Cache(manager = new BeatmapCollectionManager(LocalStorage)); - Add(manager); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Cache(manager); + dependencies.Cache(dialogOverlay); + return dependencies; } [SetUp] @@ -120,6 +120,8 @@ namespace osu.Game.Tests.Visual.Collections new BeatmapCollection { Name = { Value = "2" } }, })); + assertCollectionCount(2); + AddStep("click first delete button", () => { InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); @@ -146,6 +148,8 @@ namespace osu.Game.Tests.Visual.Collections new BeatmapCollection { Name = { Value = "2" } }, })); + assertCollectionCount(2); + AddStep("click first delete button", () => { InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); @@ -185,14 +189,16 @@ namespace osu.Game.Tests.Visual.Collections new BeatmapCollection { Name = { Value = "2" } }, })); + assertCollectionCount(2); + AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First"); } private void assertCollectionCount(int count) - => AddAssert($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); private void assertCollectionName(int index, string name) - => AddAssert($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); + => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index c2dd652b3a..dea1c4b9b4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -26,7 +26,6 @@ namespace osu.Game.Tests.Visual.SongSelect protected override Container Content => content; private readonly Container content; - [Cached] private readonly BeatmapCollectionManager collectionManager; private RulesetStore rulesets; From 32e3f5d0919abc55ab518e6d4c6dfddd9d7feb5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 13:45:26 +0900 Subject: [PATCH 073/137] Adjust button styling --- .../Select/CollectionFilterDropdown.cs | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 18caae9545..b0bd91b07d 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -166,6 +167,7 @@ namespace osu.Game.Screens.Select private IconButton addOrRemoveButton; private Content content; + private bool beatmapInCollection; public CollectionDropdownMenuItem(MenuItem item) : base(item) @@ -182,9 +184,10 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.75f), + Scale = new Vector2(0.7f), + AlwaysPresent = true, Alpha = collectionBeatmaps == null ? 0 : 1, - Action = addOrRemove + Action = addOrRemove, }); } @@ -203,24 +206,33 @@ namespace osu.Game.Screens.Select collectionName.BindValueChanged(name => content.Text = name.NewValue, true); } + protected override bool OnHover(HoverEvent e) + { + updateButtonVisibility(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateButtonVisibility(); + base.OnHoverLost(e); + } + private void collectionChanged() { Debug.Assert(collectionBeatmaps != null); - addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + beatmapInCollection = collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo); - if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) - { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusCircle; - addOrRemoveButton.IconColour = colours.Red; - } - else - { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusCircle; - addOrRemoveButton.IconColour = colours.Green; - } + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; + addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap"; + + updateButtonVisibility(); } + private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || beatmapInCollection ? 1 : 0; + private void addOrRemove() { Debug.Assert(collectionBeatmaps != null); From 8a3c8a61854d123b74b23caf9b5c389727a59592 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 14:03:49 +0900 Subject: [PATCH 074/137] Show button when selected or preselected --- osu.Game/Screens/Select/CollectionFilterDropdown.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index b0bd91b07d..c4b98fa854 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -231,7 +231,13 @@ namespace osu.Game.Screens.Select updateButtonVisibility(); } - private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || beatmapInCollection ? 1 : 0; + protected override void OnSelectChange() + { + base.OnSelectChange(); + updateButtonVisibility(); + } + + private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; private void addOrRemove() { From c2da3d9c84edfb98b6ca09efa1d9505267e3ceb0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 14:36:38 +0900 Subject: [PATCH 075/137] Fix button input and tests --- .../SongSelect/TestSceneFilterControl.cs | 67 +++++++------------ .../Select/CollectionFilterDropdown.cs | 14 ++-- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index dea1c4b9b4..65b554b27b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -109,11 +109,7 @@ namespace osu.Game.Tests.Visual.SongSelect dropdown.Current.Value = dropdown.ItemSource.ElementAt(1); }); - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First"); @@ -124,30 +120,24 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestAllBeatmapFilterDoesNotHaveAddButton() { - AddAssert("'All beatmaps' filter does not have add button", () => !getCollectionDropdownItems().First().ChildrenOfType().Single().IsPresent); + addExpandHeaderStep(); + AddStep("hover all beatmaps", () => InputManager.MoveMouseTo(getAddOrRemoveButton(0))); + AddAssert("'All beatmaps' filter does not have add button", () => !getAddOrRemoveButton(0).IsPresent); } [Test] public void TestCollectionFilterHasAddButton() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - + addExpandHeaderStep(); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("collection has add button", () => !getAddOrRemoveButton(0).IsPresent); + AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); + AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } [Test] public void TestButtonDisabledAndEnabledWithBeatmapChanges() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); @@ -161,45 +151,37 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestButtonChangesWhenAddedAndRemovedFromCollection() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } [Test] public void TestButtonAddsAndRemovesBeatmap() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) @@ -214,14 +196,17 @@ namespace osu.Game.Tests.Visual.SongSelect private IconButton getAddOrRemoveButton(int index) => getCollectionDropdownItems().ElementAt(index).ChildrenOfType().Single(); - private void addClickAddOrRemoveButtonStep(int index) + private void addExpandHeaderStep() => AddStep("expand header", () => { - AddStep("click add or remove button", () => - { - InputManager.MoveMouseTo(getAddOrRemoveButton(index)); - InputManager.Click(MouseButton.Left); - }); - } + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or remove button", () => + { + InputManager.MoveMouseTo(getAddOrRemoveButton(index)); + InputManager.Click(MouseButton.Left); + }); private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index c4b98fa854..4e9e12fcaf 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -184,9 +184,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.7f), - AlwaysPresent = true, - Alpha = collectionBeatmaps == null ? 0 : 1, + Scale = new Vector2(0.65f), Action = addOrRemove, }); } @@ -204,6 +202,8 @@ namespace osu.Game.Screens.Select // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge // of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed. collectionName.BindValueChanged(name => content.Text = name.NewValue, true); + + updateButtonVisibility(); } protected override bool OnHover(HoverEvent e) @@ -237,7 +237,13 @@ namespace osu.Game.Screens.Select updateButtonVisibility(); } - private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; + private void updateButtonVisibility() + { + if (collectionBeatmaps == null) + addOrRemoveButton.Alpha = 0; + else + addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; + } private void addOrRemove() { From 17e8171827c7b8150d376683491ded9c59b5264d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 14:38:25 +0900 Subject: [PATCH 076/137] Don't prompt to remove empty collection --- osu.Game/Collections/DeleteCollectionDialog.cs | 9 +++------ osu.Game/Collections/DrawableCollectionListItem.cs | 12 +++++++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index f2b8de7c1e..8c8c897146 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -1,7 +1,7 @@ // 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 System; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; @@ -9,10 +9,7 @@ namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } - - public DeleteCollectionDialog(BeatmapCollection collection) + public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; BodyText = collection.Name.Value; @@ -24,7 +21,7 @@ namespace osu.Game.Collections new PopupDialogOkButton { Text = @"Yes. Go for it.", - Action = () => collectionManager.Collections.Remove(collection) + Action = deleteAction }, new PopupDialogCancelButton { diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 9b7505f7c3..a1fc55556e 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -95,6 +95,9 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved] + private BeatmapCollectionManager collectionManager { get; set; } + private readonly BeatmapCollection collection; private Drawable background; @@ -146,9 +149,16 @@ namespace osu.Game.Collections protected override bool OnClick(ClickEvent e) { background.FlashColour(Color4.White, 150); - dialogOverlay?.Push(new DeleteCollectionDialog(collection)); + + if (collection.Beatmaps.Count == 0) + deleteCollection(); + else + dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); + return true; } + + private void deleteCollection() => collectionManager.Collections.Remove(collection); } } } From 1260e30cde06611ca403a972a54403879c338223 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 16:36:36 +0900 Subject: [PATCH 077/137] Make ShowDragHandle into a bindable --- .../OsuRearrangeableListContainer.cs | 4 ++-- .../Containers/OsuRearrangeableListItem.cs | 20 ++++++++++--------- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 5 ++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index 47aed1c500..1048fd094c 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -12,13 +12,13 @@ namespace osu.Game.Graphics.Containers /// /// Whether any item is currently being dragged. Used to hide other items' drag handles. /// - private readonly BindableBool playlistDragActive = new BindableBool(); + protected readonly BindableBool DragActive = new BindableBool(); protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); protected sealed override RearrangeableListItem CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d => { - d.PlaylistDragActive.BindTo(playlistDragActive); + d.DragActive.BindTo(DragActive); }); protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item); diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs index 29553954fe..9cdcb19a81 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether any item is currently being dragged. Used to hide other items' drag handles. /// - public readonly BindableBool PlaylistDragActive = new BindableBool(); + public readonly BindableBool DragActive = new BindableBool(); private Color4 handleColour = Color4.White; @@ -44,8 +44,9 @@ namespace osu.Game.Graphics.Containers /// /// Whether the drag handle should be shown. /// - protected virtual bool ShowDragHandle => true; + protected readonly Bindable ShowDragHandle = new Bindable(); + private Container handleContainer; private PlaylistItemHandle handle; protected OsuRearrangeableListItem(TModel item) @@ -58,8 +59,6 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load() { - Container handleContainer; - InternalChild = new GridContainer { RelativeSizeAxes = Axes.X, @@ -88,9 +87,12 @@ namespace osu.Game.Graphics.Containers ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }; + } - if (!ShowDragHandle) - handleContainer.Alpha = 0; + protected override void LoadComplete() + { + base.LoadComplete(); + ShowDragHandle.BindValueChanged(show => handleContainer.Alpha = show.NewValue ? 1 : 0, true); } protected override bool OnDragStart(DragStartEvent e) @@ -98,13 +100,13 @@ namespace osu.Game.Graphics.Containers if (!base.OnDragStart(e)) return false; - PlaylistDragActive.Value = true; + DragActive.Value = true; return true; } protected override void OnDragEnd(DragEndEvent e) { - PlaylistDragActive.Value = false; + DragActive.Value = false; base.OnDragEnd(e); } @@ -112,7 +114,7 @@ namespace osu.Game.Graphics.Containers protected override bool OnHover(HoverEvent e) { - handle.UpdateHoverState(IsDragged || !PlaylistDragActive.Value); + handle.UpdateHoverState(IsDragged || !DragActive.Value); return base.OnHover(e); } diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index c0892235f2..b007e0349d 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Multi public readonly Bindable SelectedItem = new Bindable(); - protected override bool ShowDragHandle => allowEdit; - private Container maskingContainer; private Container difficultyIconContainer; private LinkFlowContainer beatmapText; @@ -63,12 +61,13 @@ namespace osu.Game.Screens.Multi // TODO: edit support should be moved out into a derived class this.allowEdit = allowEdit; - this.allowSelection = allowSelection; beatmap.BindTo(item.Beatmap); ruleset.BindTo(item.Ruleset); requiredMods.BindTo(item.RequiredMods); + + ShowDragHandle.Value = allowEdit; } [BackgroundDependencyLoader] From 0bf6bfe5ee4569bc7b2c0e83e92dd659d33299fc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 16:43:07 +0900 Subject: [PATCH 078/137] Create a new collection via a placeholder item --- .../Collections/DrawableCollectionList.cs | 97 +++++++++++++++++-- .../Collections/DrawableCollectionListItem.cs | 94 ++++++++++++++---- .../Collections/ManageCollectionsDialog.cs | 19 +--- 3 files changed, 163 insertions(+), 47 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index e8bde9066f..f4b5a89b3e 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -1,6 +1,8 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -10,19 +12,94 @@ namespace osu.Game.Collections { public class DrawableCollectionList : OsuRearrangeableListContainer { - protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => - { - d.ScrollbarVisible = false; - d.Padding = new MarginPadding(10); - }); + private Scroll scroll; - protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> + protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); + + protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow { - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - Spacing = new Vector2(0, 5) + DragActive = { BindTarget = DragActive } }; - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new DrawableCollectionListItem(item); + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) + { + if (item == scroll.PlaceholderItem.Model) + return scroll.ReplacePlaceholder(); + + return new DrawableCollectionListItem(item, true); + } + + private class Scroll : OsuScrollContainer + { + public DrawableCollectionListItem PlaceholderItem { get; private set; } + + protected override Container Content => content; + private readonly Container content; + + private readonly Container placeholderContainer; + + public Scroll() + { + ScrollbarVisible = false; + Padding = new MarginPadding(10); + + base.Content.Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + Children = new Drawable[] + { + content = new Container { RelativeSizeAxes = Axes.X }, + placeholderContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + }); + + ReplacePlaceholder(); + } + + protected override void Update() + { + base.Update(); + + // AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around. + content.Height = ((Flow)Child).Children.Sum(c => c.DrawHeight + 5); + } + + /// + /// Replaces the current with a new one, and returns the previous. + /// + public DrawableCollectionListItem ReplacePlaceholder() + { + var previous = PlaceholderItem; + + placeholderContainer.Clear(false); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); + + return previous; + } + } + + private class Flow : FillFlowContainer> + { + public readonly IBindable DragActive = new Bindable(); + + public Flow() + { + Spacing = new Vector2(0, 5); + LayoutEasing = Easing.OutQuint; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + DragActive.BindValueChanged(active => LayoutDuration = active.NewValue ? 200 : 0); + } + } } } diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a1fc55556e..90d5bae223 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -21,20 +22,33 @@ namespace osu.Game.Collections public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; - private const float button_width = item_height * 0.75f; - public DrawableCollectionListItem(BeatmapCollection item) + private readonly Bindable isCreated = new Bindable(); + + public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { + this.isCreated.Value = isCreated; + + ShowDragHandle.BindTo(this.isCreated); } - protected override Drawable CreateContent() => new ItemContent(Model); + protected override Drawable CreateContent() => new ItemContent(Model) + { + IsCreated = { BindTarget = isCreated } + }; private class ItemContent : CircularContainer { + public readonly Bindable IsCreated = new Bindable(); + + private readonly IBindable collectionName; private readonly BeatmapCollection collection; + [Resolved] + private BeatmapCollectionManager collectionManager { get; set; } + private ItemTextBox textBox; public ItemContent(BeatmapCollection collection) @@ -44,6 +58,8 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.X; Height = item_height; Masking = true; + + collectionName = collection.Name.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -55,6 +71,7 @@ namespace osu.Game.Collections { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + IsCreated = { BindTarget = IsCreated }, IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) }, new Container @@ -68,12 +85,37 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Size = Vector2.One, CornerRadius = item_height / 2, - Current = collection.Name + Current = collection.Name, + PlaceholderText = IsCreated.Value ? string.Empty : "Create a new collection" }, } }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + collectionName.BindValueChanged(_ => createNewCollection(), true); + } + + private void createNewCollection() + { + if (IsCreated.Value) + return; + + if (string.IsNullOrEmpty(collectionName.Value)) + return; + + // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. + collectionManager.Collections.Add(collection); + textBox.PlaceholderText = string.Empty; + + // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. + Schedule(() => GetContainingInputManager().ChangeFocus(textBox)); + + IsCreated.Value = true; + } } private class ItemTextBox : OsuTextBox @@ -90,6 +132,8 @@ namespace osu.Game.Collections public class DeleteButton : CompositeDrawable { + public readonly IBindable IsCreated = new Bindable(); + public Func IsTextBoxHovered; [Resolved(CanBeNull = true)] @@ -100,6 +144,7 @@ namespace osu.Game.Collections private readonly BeatmapCollection collection; + private Drawable fadeContainer; private Drawable background; public DeleteButton(BeatmapCollection collection) @@ -108,42 +153,51 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Y; Width = button_width + item_height / 2; // add corner radius to cover with fill - - Alpha = 0.1f; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChildren = new[] + InternalChild = fadeContainer = new Container { - background = new Box + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.Red - }, - new SpriteIcon - { - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre, - X = -button_width * 0.6f, - Size = new Vector2(10), - Icon = FontAwesome.Solid.Trash + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Red + }, + new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + X = -button_width * 0.6f, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Trash + } } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + IsCreated.BindValueChanged(created => Alpha = created.NewValue ? 1 : 0, true); + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); protected override bool OnHover(HoverEvent e) { - this.FadeTo(1f, 100, Easing.Out); + fadeContainer.FadeTo(1f, 100, Easing.Out); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.FadeTo(0.1f, 100); + fadeContainer.FadeTo(0.1f, 100); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 036a745913..8f8ac9542c 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Collections @@ -51,9 +50,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 50), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 50), + new Dimension(GridSizeMode.AutoSize), }, Content = new[] { @@ -65,6 +62,7 @@ namespace osu.Game.Collections Origin = Anchor.Centre, Text = "Manage collections", Font = OsuFont.GetFont(size: 30), + Padding = new MarginPadding { Vertical = 10 }, } }, new Drawable[] @@ -87,19 +85,6 @@ namespace osu.Game.Collections } } }, - new Drawable[] - { - new OsuButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = Vector2.One, - Padding = new MarginPadding(10), - Text = "Create new collection", - Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "My new collection" } }) - }, - }, } } } From 38ade433a62b449be687ba3409cfd854d4d04876 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 16:50:51 +0900 Subject: [PATCH 079/137] Add some xmldocs --- osu.Game/Collections/DrawableCollectionList.cs | 17 +++++++++++++++++ .../Collections/DrawableCollectionListItem.cs | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index f4b5a89b3e..3c664a11d9 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -10,6 +10,9 @@ using osuTK; namespace osu.Game.Collections { + /// + /// Visualises a list of s. + /// public class DrawableCollectionList : OsuRearrangeableListContainer { private Scroll scroll; @@ -29,8 +32,18 @@ namespace osu.Game.Collections return new DrawableCollectionListItem(item, true); } + /// + /// The scroll container for this . + /// Contains the main flow of and attaches a placeholder item to the end of the list. + /// + /// + /// Use to transfer the placeholder into the main list. + /// private class Scroll : OsuScrollContainer { + /// + /// The currently-displayed placeholder item. + /// public DrawableCollectionListItem PlaceholderItem { get; private set; } protected override Container Content => content; @@ -74,6 +87,7 @@ namespace osu.Game.Collections /// /// Replaces the current with a new one, and returns the previous. /// + /// The current . public DrawableCollectionListItem ReplacePlaceholder() { var previous = PlaceholderItem; @@ -85,6 +99,9 @@ namespace osu.Game.Collections } } + /// + /// The flow of . Disables layout easing unless a drag is in progress. + /// private class Flow : FillFlowContainer> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 90d5bae223..489382ec9e 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -19,6 +19,9 @@ using osuTK.Graphics; namespace osu.Game.Collections { + /// + /// Visualises a inside a . + /// public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; @@ -26,6 +29,11 @@ namespace osu.Game.Collections private readonly Bindable isCreated = new Bindable(); + /// + /// Creates a new . + /// + /// The . + /// Whether currently exists inside the . public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { @@ -39,6 +47,9 @@ namespace osu.Game.Collections IsCreated = { BindTarget = isCreated } }; + /// + /// The main content of the . + /// private class ItemContent : CircularContainer { public readonly Bindable IsCreated = new Bindable(); From 2e40ff25f7228ed74aaf12fbd83ada10b4a0a917 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:05:31 +0900 Subject: [PATCH 080/137] Only pad textbox after collection is created --- osu.Game/Collections/DrawableCollectionListItem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 489382ec9e..c67946977d 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -60,6 +60,7 @@ namespace osu.Game.Collections [Resolved] private BeatmapCollectionManager collectionManager { get; set; } + private Container textBoxPaddingContainer; private ItemTextBox textBox; public ItemContent(BeatmapCollection collection) @@ -85,7 +86,7 @@ namespace osu.Game.Collections IsCreated = { BindTarget = IsCreated }, IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) }, - new Container + textBoxPaddingContainer = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = button_width }, @@ -107,7 +108,9 @@ namespace osu.Game.Collections protected override void LoadComplete() { base.LoadComplete(); + collectionName.BindValueChanged(_ => createNewCollection(), true); + IsCreated.BindValueChanged(created => textBoxPaddingContainer.Padding = new MarginPadding { Right = created.NewValue ? button_width : 0 }, true); } private void createNewCollection() From bee450ae1ecba24a161e681e53e53f94462428fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:05:43 +0900 Subject: [PATCH 081/137] Fix tests/add placeholder item tests --- .../TestSceneManageCollectionsDialog.cs | 80 ++++++++++++++----- .../SongSelect/TestSceneFilterControl.cs | 14 ++-- .../Collections/DrawableCollectionListItem.cs | 5 ++ 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index fdaded6a5c..0c57c27911 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -7,11 +7,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Collections; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -25,6 +28,9 @@ namespace osu.Game.Tests.Visual.Collections private readonly DialogOverlay dialogOverlay; private readonly BeatmapCollectionManager manager; + private RulesetStore rulesets; + private BeatmapManager beatmapManager; + private ManageCollectionsDialog dialog; public TestSceneManageCollectionsDialog() @@ -37,6 +43,15 @@ namespace osu.Game.Tests.Visual.Collections }); } + [BackgroundDependencyLoader] + private void load(GameHost host) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -65,6 +80,12 @@ namespace osu.Game.Tests.Visual.Collections AddStep("hide dialog", () => dialog.Hide()); } + [Test] + public void TestLastItemIsPlaceholder() + { + AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); + } + [Test] public void TestAddCollectionExternal() { @@ -78,23 +99,34 @@ namespace osu.Game.Tests.Visual.Collections } [Test] - public void TestAddCollectionViaButton() + public void TestFocusPlaceholderDoesNotCreateCollection() { - AddStep("press new collection button", () => + AddStep("focus placeholder", () => { - InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); + InputManager.MoveMouseTo(dialog.ChildrenOfType().Last()); InputManager.Click(MouseButton.Left); }); + assertCollectionCount(0); + } + + [Test] + public void TestAddCollectionViaPlaceholder() + { + DrawableCollectionListItem placeholderItem = null; + + AddStep("focus placeholder", () => + { + InputManager.MoveMouseTo(placeholderItem = dialog.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + + // Done directly via the collection since InputManager methods cannot add text to textbox... + AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a"); assertCollectionCount(1); + AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model)); - AddStep("press again", () => - { - InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - - assertCollectionCount(2); + AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); } [Test] @@ -117,7 +149,7 @@ namespace osu.Game.Tests.Visual.Collections AddStep("add two collections", () => manager.Collections.AddRange(new[] { new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, + new BeatmapCollection { Name = { Value = "2" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } }, })); assertCollectionCount(2); @@ -128,6 +160,16 @@ namespace osu.Game.Tests.Visual.Collections InputManager.Click(MouseButton.Left); }); + AddAssert("dialog not displayed", () => dialogOverlay.CurrentDialog == null); + assertCollectionCount(1); + assertCollectionName(0, "2"); + + AddStep("click first delete button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.Click(MouseButton.Left); + }); + AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); AddStep("click confirmation", () => { @@ -135,8 +177,7 @@ namespace osu.Game.Tests.Visual.Collections InputManager.Click(MouseButton.Left); }); - assertCollectionCount(1); - assertCollectionName(0, "2"); + assertCollectionCount(0); } [Test] @@ -144,11 +185,10 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add two collections", () => manager.Collections.AddRange(new[] { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, + new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } }, })); - assertCollectionCount(2); + assertCollectionCount(1); AddStep("click first delete button", () => { @@ -157,13 +197,13 @@ namespace osu.Game.Tests.Visual.Collections }); AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); - AddStep("click confirmation", () => + AddStep("click cancellation", () => { InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType().Last()); InputManager.Click(MouseButton.Left); }); - assertCollectionCount(2); + assertCollectionCount(1); } [Test] @@ -196,7 +236,7 @@ namespace osu.Game.Tests.Visual.Collections } private void assertCollectionCount(int count) - => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsCreated.Value) == count); private void assertCollectionName(int index, string name) => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 65b554b27b..4606c7b0c3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -42,13 +42,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(collectionManager); - return dependencies; - } - [BackgroundDependencyLoader] private void load(GameHost host) { @@ -58,6 +51,13 @@ namespace osu.Game.Tests.Visual.SongSelect beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Cache(collectionManager); + return dependencies; + } + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index c67946977d..a7075c3179 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -27,6 +27,11 @@ namespace osu.Game.Collections private const float item_height = 35; private const float button_width = item_height * 0.75f; + /// + /// Whether the currently exists inside the . + /// + public IBindable IsCreated => isCreated; + private readonly Bindable isCreated = new Bindable(); /// From d2650fc1a05e012a58a2283a59ce4dfdc6e634e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:12:58 +0900 Subject: [PATCH 082/137] Add count to deletion dialog --- osu.Game/Collections/DeleteCollectionDialog.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 8c8c897146..e5a2f6fb81 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; @@ -12,7 +13,7 @@ namespace osu.Game.Collections public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; - BodyText = collection.Name.Value; + BodyText = $"{collection.Name.Value} ({"beatmap".ToQuantity(collection.Beatmaps.Count)})"; Icon = FontAwesome.Regular.TrashAlt; From 4737add00bc99290652f585bf85a6acbc4d70c55 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:21:29 +0900 Subject: [PATCH 083/137] Add close button to dialog --- .../Collections/ManageCollectionsDialog.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 8f8ac9542c..f6964191a1 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -5,9 +5,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Collections @@ -56,13 +58,31 @@ namespace osu.Game.Collections { new Drawable[] { - new OsuSpriteText + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Manage collections", - Font = OsuFont.GetFont(size: 30), - Padding = new MarginPadding { Vertical = 10 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Manage collections", + Font = OsuFont.GetFont(size: 30), + Padding = new MarginPadding { Vertical = 10 }, + }, + new IconButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Icon = FontAwesome.Solid.Times, + Colour = colours.GreySeafoamDarker, + Scale = new Vector2(0.8f), + X = -10, + Action = () => State.Value = Visibility.Hidden + } + } } }, new Drawable[] From c3123bf11712937fed58039477442717e7e8076b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 17:22:59 +0900 Subject: [PATCH 084/137] Rename drag blueprint selection method for discoverability --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index fcff672045..865e225645 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AddRangeInternal(new[] { - DragBox = CreateDragBox(select), + DragBox = CreateDragBox(selectBlueprintsFromDragRectangle), selectionHandler, SelectionBlueprints = CreateSelectionBlueprintContainer(), selectionHandler.CreateProxy(), @@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Select all masks in a given rectangle selection area. /// /// The rectangle to perform a selection on in screen-space coordinates. - private void select(RectangleF rect) + private void selectBlueprintsFromDragRectangle(RectangleF rect) { foreach (var blueprint in SelectionBlueprints) { From 06328e00001315d83d70cfabef4a350d434d8399 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:58:56 +0900 Subject: [PATCH 085/137] Add import/deletion progress notifications --- .../Collections/BeatmapCollectionManager.cs | 56 ++++++++++++++++++- osu.Game/OsuGame.cs | 1 + .../Sections/Maintenance/GeneralSettings.cs | 2 +- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index a553ac632e..6a5ed6bbbc 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -57,6 +57,10 @@ namespace osu.Game.Collections c.Changed += backgroundSave; Collections.CollectionChanged += (_, __) => backgroundSave(); } + /// + /// Set an endpoint for notifications to be posted to. + /// + public Action PostNotification { protected get; set; } /// /// Set a storage with access to an osu-stable install for import purposes. @@ -93,9 +97,25 @@ namespace osu.Game.Collections }); } - public async Task Import(Stream stream) => await Task.Run(async () => + public async Task Import(Stream stream) { - var collection = readCollections(stream); + var notification = new ProgressNotification + { + State = ProgressNotificationState.Active, + Text = "Collections import is initialising..." + }; + + PostNotification?.Invoke(notification); + + await import(stream, notification); + } + + private async Task import(Stream stream, ProgressNotification notification = null) => await Task.Run(async () => + { + if (notification != null) + notification.Progress = 0; + + var collection = readCollections(stream, notification); bool importCompleted = false; Schedule(() => @@ -106,6 +126,12 @@ namespace osu.Game.Collections while (!IsDisposed && !importCompleted) await Task.Delay(10); + + if (notification != null) + { + notification.CompletionText = $"Imported {collection.Count} collections"; + notification.State = ProgressNotificationState.Completed; + } }); private void importCollections(List newCollections) @@ -124,8 +150,14 @@ namespace osu.Game.Collections } } - private List readCollections(Stream stream) + private List readCollections(Stream stream, ProgressNotification notification = null) { + if (notification != null) + { + notification.Text = "Reading collections..."; + notification.Progress = 0; + } + var result = new List(); try @@ -139,11 +171,17 @@ namespace osu.Game.Collections for (int i = 0; i < collectionCount; i++) { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + string checksum = sr.ReadString(); var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); @@ -151,6 +189,12 @@ namespace osu.Game.Collections collection.Beatmaps.Add(beatmap); } + if (notification != null) + { + notification.Text = $"Imported {i + 1} of {collectionCount} collections"; + notification.Progress = (float)(i + 1) / collectionCount; + } + result.Add(collection); } } @@ -163,6 +207,12 @@ namespace osu.Game.Collections return result; } + public void DeleteAll() + { + Collections.Clear(); + PostNotification?.Invoke(new SimpleNotification { Text = "Deleted all collections!" }); + } + private readonly object saveLock = new object(); private int lastSave; private int saveFailures; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8434ee11fa..33a353742d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -537,6 +537,7 @@ namespace osu.Game ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.PresentImport = items => PresentScore(items.First()); + CollectionManager.PostNotification = n => notifications.Post(n); CollectionManager.GetStableStorage = GetStorageForStableInstall; Container logoContainer; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 74f9920ae0..30fd5921eb 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = "Delete ALL collections", Action = () => { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collectionManager.Collections.Clear())); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll)); } }); From 070704cba719a124bd48c6b5a9133ddaae157e92 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:59:38 +0900 Subject: [PATCH 086/137] Asyncify initial load --- .../Collections/BeatmapCollectionManager.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 6a5ed6bbbc..e4fc4c377b 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading; @@ -15,6 +16,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; +using osu.Game.Overlays.Notifications; namespace osu.Game.Collections { @@ -46,17 +48,46 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() + { + Collections.CollectionChanged += collectionsChanged; + loadDatabase(); + } + + private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var c in e.NewItems.Cast()) + c.Changed += backgroundSave; + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var c in e.OldItems.Cast()) + c.Changed -= backgroundSave; + break; + + case NotifyCollectionChangedAction.Replace: + foreach (var c in e.OldItems.Cast()) + c.Changed -= backgroundSave; + + foreach (var c in e.NewItems.Cast()) + c.Changed += backgroundSave; + break; + } + + backgroundSave(); + } + + private void loadDatabase() => Task.Run(async () => { if (storage.Exists(database_name)) { using (var stream = storage.GetStream(database_name)) - importCollections(readCollections(stream)); + await import(stream); } + }); - foreach (var c in Collections) - c.Changed += backgroundSave; - Collections.CollectionChanged += (_, __) => backgroundSave(); - } /// /// Set an endpoint for notifications to be posted to. /// From b1110e5e3a1778a4cf5075728194e524c468f0c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:10:14 +0900 Subject: [PATCH 087/137] Rename class to match derived class --- osu.Game/OsuGame.cs | 2 +- .../Music/{MusicActionHandler.cs => MusicKeyBindingHandler.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/Overlays/Music/{MusicActionHandler.cs => MusicKeyBindingHandler.cs} (96%) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a73469d836..b4e671d0b0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -648,7 +648,7 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); - Add(new MusicActionHandler()); + Add(new MusicKeyBindingHandler()); // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; diff --git a/osu.Game/Overlays/Music/MusicActionHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs similarity index 96% rename from osu.Game/Overlays/Music/MusicActionHandler.cs rename to osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index cd8548c1c0..78e6ba1381 100644 --- a/osu.Game/Overlays/Music/MusicActionHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Music /// /// Handles relating to music playback, and displays a via the cached accordingly. /// - public class MusicActionHandler : Component, IKeyBindingHandler + public class MusicKeyBindingHandler : Component, IKeyBindingHandler { [Resolved] private IBindable beatmap { get; set; } From a46be45a71b9b70ca1b70b68d54d33a625070a4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:12:03 +0900 Subject: [PATCH 088/137] Fix OSD occasionally display incorrect play/pause state --- osu.Game/Overlays/Music/MusicKeyBindingHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 78e6ba1381..277fb1a35d 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -33,9 +33,11 @@ namespace osu.Game.Overlays.Music switch (action) { case GlobalAction.MusicPlay: - if (musicController.TogglePause()) - onScreenDisplay?.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); + // use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842) + bool wasPlaying = musicController.IsPlaying; + if (musicController.TogglePause()) + onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track")); return true; case GlobalAction.MusicNext: From 14bf2ab936b38c16cccbac254293d28791002b33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:21:26 +0900 Subject: [PATCH 089/137] Fix grammar in xmldoc --- osu.Game/Overlays/Music/MusicKeyBindingHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 78e6ba1381..02196348ae 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -12,7 +12,7 @@ using osu.Game.Overlays.OSD; namespace osu.Game.Overlays.Music { /// - /// Handles relating to music playback, and displays a via the cached accordingly. + /// Handles s related to music playback, and displays s via the global accordingly. /// public class MusicKeyBindingHandler : Component, IKeyBindingHandler { From f581df47c8edcf03771ae1e019323f0a23301995 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:25:09 +0900 Subject: [PATCH 090/137] Add "New collection..." item to dropdown --- .../SongSelect/TestSceneFilterControl.cs | 23 +++++++++++++++++++ osu.Game/Screens/Select/CollectionFilter.cs | 17 ++++++++++++++ .../Select/CollectionFilterDropdown.cs | 18 ++++++++++++--- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 4606c7b0c3..955fe04c8c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -184,6 +184,29 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } + [Test] + public void TestNewCollectionFilterIsNotSelected() + { + addExpandHeaderStep(); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("select collection", () => + { + InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + + addExpandHeaderStep(); + + AddStep("click manage collections filter", () => + { + InputManager.MoveMouseTo(getCollectionDropdownItems().Last()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.CollectionName.Value == "1"); + } + private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs index 7628ed391e..9e36e3e089 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionFilter.cs @@ -45,4 +45,21 @@ namespace osu.Game.Screens.Select public virtual bool ContainsBeatmap(BeatmapInfo beatmap) => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; } + + public class AllBeatmapCollectionFilter : CollectionFilter + { + public AllBeatmapCollectionFilter() + : base(null) + { + } + } + + public class NewCollectionFilter : CollectionFilter + { + public NewCollectionFilter() + : base(null) + { + CollectionName.Value = "New collection..."; + } + } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 4e9e12fcaf..5e5c684fe2 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -29,6 +29,9 @@ namespace osu.Game.Screens.Select private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); + [Resolved(CanBeNull = true)] + private ManageCollectionsDialog manageCollectionsDialog { get; set; } + public CollectionFilterDropdown() { ItemSource = filters; @@ -57,10 +60,11 @@ namespace osu.Game.Screens.Select var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); - filters.Add(new CollectionFilter(null)); + filters.Add(new AllBeatmapCollectionFilter()); filters.AddRange(collections.Select(c => new CollectionFilter(c))); + filters.Add(new NewCollectionFilter()); - Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } /// @@ -78,6 +82,14 @@ namespace osu.Game.Screens.Select beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); beatmaps.CollectionChanged += filterBeatmapsChanged; + + // Never select the manage collection filter - rollback to the previous filter. + // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. + if (filter.NewValue is NewCollectionFilter) + { + Current.Value = filter.OldValue; + manageCollectionsDialog?.Show(); + } } /// @@ -90,7 +102,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; + protected override string GenerateItemText(CollectionFilter item) => item.CollectionName.Value; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader { From ad5d6117c76856237d2215a89f8c2f7c2ab7524d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:26:13 +0900 Subject: [PATCH 091/137] Remove unnecessary RunTask calls --- .../Overlays/Music/MusicKeyBindingHandler.cs | 7 ++----- osu.Game/Overlays/MusicController.cs | 17 ++++------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 02196348ae..f5968614cd 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -39,10 +39,7 @@ namespace osu.Game.Overlays.Music return true; case GlobalAction.MusicNext: - musicController.NextTrack(() => - { - onScreenDisplay?.Display(new MusicActionToast("Next track")); - }).RunTask(); + musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track"))); return true; @@ -59,7 +56,7 @@ namespace osu.Game.Overlays.Music onScreenDisplay?.Display(new MusicActionToast("Previous track")); break; } - }).RunTask(); + }); return true; } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a405be1b74..b568e4d02b 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -201,13 +201,8 @@ namespace osu.Game.Overlays /// /// Play the previous track or restart the current track if it's current time below . /// - /// - /// Invoked when the operation has been performed successfully. - /// The result isn't returned directly to the caller because - /// the operation is scheduled and isn't performed immediately. - /// - /// A of the operation. - public ScheduledDelegate PreviousTrack(Action onSuccess = null) => Schedule(() => + /// Invoked when the operation has been performed successfully. + public void PreviousTrack(Action onSuccess = null) => Schedule(() => { PreviousTrackResult res = prev(); if (res != PreviousTrackResult.None) @@ -248,13 +243,9 @@ namespace osu.Game.Overlays /// /// Play the next random or playlist track. /// - /// - /// Invoked when the operation has been performed successfully. - /// The result isn't returned directly to the caller because - /// the operation is scheduled and isn't performed immediately. - /// + /// Invoked when the operation has been performed successfully. /// A of the operation. - public ScheduledDelegate NextTrack(Action onSuccess = null) => Schedule(() => + public void NextTrack(Action onSuccess = null) => Schedule(() => { bool res = next(); if (res) From e1053c4b6f9376884786084e0d48a26962f6edcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:36:11 +0900 Subject: [PATCH 092/137] Revert exposure changes to GlobalActionContainer --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 13 ++++++++----- .../Visual/Navigation/OsuGameTestScene.cs | 3 --- osu.Game/OsuGameBase.cs | 11 ++++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9b8ba47992..4cad2b19d5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -14,13 +15,15 @@ namespace osu.Game.Tests.Visual.Menus { public class TestSceneMusicActionHandling : OsuGameTestScene { + private GlobalActionContainer globalActionContainer => Game.ChildrenOfType().First(); + [Test] public void TestMusicPlayAction() { AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); - AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused); - AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused); } @@ -62,16 +65,16 @@ namespace osu.Game.Tests.Visual.Menus AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000); - AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("no track change", () => trackChangeQueue.Count == 0); AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); - AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("track changed to previous", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); - AddStep("press next", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); AddAssert("track changed to next", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index e29d23ba75..c4acf4f7da 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -14,7 +14,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -110,8 +109,6 @@ namespace osu.Game.Tests.Visual.Navigation public new OsuConfigManager LocalConfig => base.LocalConfig; - public new GlobalActionContainer GlobalBinding => base.GlobalBinding; - public new Bindable Beatmap => base.Beatmap; public new Bindable Ruleset => base.Ruleset; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8e01bda6ec..4bc8f4c527 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -64,8 +64,6 @@ namespace osu.Game protected FileStore FileStore; - protected GlobalActionContainer GlobalBinding; - protected KeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -253,7 +251,10 @@ namespace osu.Game AddInternal(RulesetConfigCache); MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; - MenuCursorContainer.Child = GlobalBinding = new GlobalActionContainer(this) + + GlobalActionContainer globalBindings; + + MenuCursorContainer.Child = globalBindings = new GlobalActionContainer(this) { RelativeSizeAxes = Axes.Both, Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } @@ -261,8 +262,8 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); - KeyBindingStore.Register(GlobalBinding); - dependencies.Cache(GlobalBinding); + KeyBindingStore.Register(globalBindings); + dependencies.Cache(globalBindings); PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); From 4962213cc499eecf9df2f876fc8b14672b11b104 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:42:55 +0900 Subject: [PATCH 093/137] Rename manage collections filter/text --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 2 +- osu.Game/Screens/Select/CollectionFilter.cs | 6 +++--- osu.Game/Screens/Select/CollectionFilterDropdown.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 955fe04c8c..5b0e244bbe 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestNewCollectionFilterIsNotSelected() + public void TestManageCollectionsFilterIsNotSelected() { addExpandHeaderStep(); diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs index 9e36e3e089..883019ab06 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionFilter.cs @@ -54,12 +54,12 @@ namespace osu.Game.Screens.Select } } - public class NewCollectionFilter : CollectionFilter + public class ManageCollectionsFilter : CollectionFilter { - public NewCollectionFilter() + public ManageCollectionsFilter() : base(null) { - CollectionName.Value = "New collection..."; + CollectionName.Value = "Manage collections..."; } } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 5e5c684fe2..6b9ae1b5c8 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Select filters.Clear(); filters.Add(new AllBeatmapCollectionFilter()); filters.AddRange(collections.Select(c => new CollectionFilter(c))); - filters.Add(new NewCollectionFilter()); + filters.Add(new ManageCollectionsFilter()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. - if (filter.NewValue is NewCollectionFilter) + if (filter.NewValue is ManageCollectionsFilter) { Current.Value = filter.OldValue; manageCollectionsDialog?.Show(); From ae022d755964536843265ab1e1b7169edeef40a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:55:53 +0900 Subject: [PATCH 094/137] Show all items in dropdown, set global max height --- osu.Game/Graphics/UserInterface/OsuContextMenu.cs | 2 ++ osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 4b629080e1..8c7b44f952 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -26,6 +26,8 @@ namespace osu.Game.Graphics.UserInterface }; ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL }; + + MaxHeight = 250; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 9f21ec1ad1..cf1c51acd1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 19ecc277c4..2c098291fa 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); From a5e1e8d043c98438e748d4c956febb6e29900a56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:57:18 +0900 Subject: [PATCH 095/137] Rename More... to Manage... --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index cf1c51acd1..e9990ab078 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Select.Carousel var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 2c098291fa..fe700f12df 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Select.Carousel var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); From 2b4e2d8ed63c4500aba1fb3236fb481469caab6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:04:46 +0900 Subject: [PATCH 096/137] Standardise corner radius of dropdowns --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index fc3a7229fa..cc76c12975 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -17,6 +17,8 @@ namespace osu.Game.Graphics.UserInterface { public class OsuDropdown : Dropdown, IHasAccentColour { + private const float corner_radius = 4; + private Color4 accentColour; public Color4 AccentColour @@ -57,9 +59,11 @@ namespace osu.Game.Graphics.UserInterface // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring public OsuDropdownMenu() { - CornerRadius = 4; + CornerRadius = corner_radius; BackgroundColour = Color4.Black.Opacity(0.5f); + MaskingContainer.CornerRadius = corner_radius; + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring ItemsContainer.Padding = new MarginPadding(5); } @@ -138,7 +142,7 @@ namespace osu.Game.Graphics.UserInterface Foreground.Padding = new MarginPadding(2); Masking = true; - CornerRadius = 6; + CornerRadius = corner_radius; } [BackgroundDependencyLoader] @@ -237,7 +241,7 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.None; Margin = new MarginPadding { Bottom = 4 }; - CornerRadius = 4; + CornerRadius = corner_radius; Height = 40; Foreground.Children = new Drawable[] From b7ca0039282769ea2388da23af8e9884a7c265c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 19:14:48 +0900 Subject: [PATCH 097/137] Remove unnecessary check --- osu.Game/Collections/BeatmapCollectionManager.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index e4fc4c377b..c14b67a7e8 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -120,11 +120,8 @@ namespace osu.Game.Collections return Task.Run(async () => { - if (stable.Exists(database_name)) - { - using (var stream = stable.GetStream(database_name)) - await Import(stream); - } + using (var stream = stable.GetStream(database_name)) + await Import(stream); }); } From 8e2f5d4ea85be3d3f62678d2a84094e52ed15d37 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 19:41:05 +0900 Subject: [PATCH 098/137] Fix test failures --- .../Collections/IO/ImportCollectionsTest.cs | 55 ++++++++++++------- .../Collections/BeatmapCollectionManager.cs | 7 +++ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 7d772d3989..95013859f0 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Tests.Resources; @@ -21,11 +21,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportEmptyDatabase() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportEmptyDatabase")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host); + var osu = loadOsu(host); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(new MemoryStream()); @@ -42,11 +42,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportWithNoBeatmaps() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithNoBeatmaps")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host); + var osu = loadOsu(host); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -69,11 +69,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportWithBeatmaps() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithBeatmaps")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -99,13 +99,13 @@ namespace osu.Game.Tests.Collections.IO bool exceptionThrown = false; UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true; - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMalformedDatabase")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { AppDomain.CurrentDomain.UnhandledException += setException; - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); @@ -137,11 +137,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestSaveAndReload() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestSaveAndReload")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); @@ -182,9 +182,9 @@ namespace osu.Game.Tests.Collections.IO } } - private async Task loadOsu(GameHost host, bool withBeatmap = false) + private OsuGameBase loadOsu(GameHost host, bool withBeatmap = false) { - var osu = new OsuGameBase(); + var osu = new TestOsuGameBase(withBeatmap); #pragma warning disable 4014 Task.Run(() => host.Run(osu)); @@ -192,12 +192,8 @@ namespace osu.Game.Tests.Collections.IO waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - if (withBeatmap) - { - var beatmapFile = TestResources.GetTestBeatmapForImport(); - var beatmapManager = osu.Dependencies.Get(); - await beatmapManager.Import(beatmapFile); - } + var collectionManager = osu.Dependencies.Get(); + waitForOrAssert(() => collectionManager.DatabaseLoaded, "Collection database did not load in a reasonable amount of time"); return osu; } @@ -211,5 +207,24 @@ namespace osu.Game.Tests.Collections.IO Assert.IsTrue(task.Wait(timeout), failureMessage); } + + private class TestOsuGameBase : OsuGameBase + { + private readonly bool withBeatmap; + + public TestOsuGameBase(bool withBeatmap) + { + this.withBeatmap = withBeatmap; + } + + protected override void AddInternal(Drawable drawable) + { + // The beatmap must be imported just before the collection manager is loaded. + if (drawable is BeatmapCollectionManager && withBeatmap) + BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + + base.AddInternal(drawable); + } + } } } diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index c14b67a7e8..ed41627d63 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -33,6 +33,11 @@ namespace osu.Game.Collections public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + /// + /// Whether the user's database has finished loading. + /// + public bool DatabaseLoaded { get; private set; } + [Resolved] private GameHost host { get; set; } @@ -86,6 +91,8 @@ namespace osu.Game.Collections using (var stream = storage.GetStream(database_name)) await import(stream); } + + DatabaseLoaded = true; }); /// From a501df954b3337ef6782a07aeb565a0172c7c7b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:50:29 +0900 Subject: [PATCH 099/137] Avoid multiple editor screens potentially loading on top of each other --- osu.Game/Screens/Edit/Editor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3ba3eb108a..ac1f61c4fd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -398,7 +398,11 @@ namespace osu.Game.Screens.Edit break; } - LoadComponentAsync(currentScreen, screenContainer.Add); + LoadComponentAsync(currentScreen, newScreen => + { + if (newScreen == currentScreen) + screenContainer.Add(newScreen); + }); } private void seek(UIEvent e, int direction) From 379fdadbe54e34ea99e57a86106b94bdd9b8bcd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 18:47:14 +0900 Subject: [PATCH 100/137] Add test scene for setup screen --- .../Visual/Editing/TestSceneSetupScreen.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs new file mode 100644 index 0000000000..62e12158ab --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; + +namespace osu.Game.Tests.Visual.Editing +{ + [TestFixture] + public class TestSceneSetupScreen : EditorClockTestScene + { + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + public TestSceneSetupScreen() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [BackgroundDependencyLoader] + private void load() + { + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Child = new SetupScreen(); + } + } +} From f43f8cf6b95bebf5c18683acdb0e96a6ff731fb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 19:21:35 +0900 Subject: [PATCH 101/137] Add basic setup for song select screen --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 74 +++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 758dbc6e16..84e96a14e2 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,13 +1,83 @@ // 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.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + namespace osu.Game.Screens.Edit.Setup { public class SetupScreen : EditorScreen { - public SetupScreen() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - Child = new ScreenWhiteBox.UnderConstructionMessage("Setup mode"); + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray0, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(50), + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 250, + Masking = true, + CornerRadius = 50, + Child = new BeatmapBackgroundSprite(Beatmap.Value) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + }, + }, + new OsuSpriteText + { + Text = "Beatmap metadata" + }, + new LabelledTextBox + { + Label = "Artist", + Current = { Value = Beatmap.Value.Metadata.Artist } + }, + new LabelledTextBox + { + Label = "Title", + Current = { Value = Beatmap.Value.Metadata.Title } + }, + new LabelledTextBox + { + Label = "Creator", + Current = { Value = Beatmap.Value.Metadata.AuthorString } + }, + new LabelledTextBox + { + Label = "Difficulty Name", + Current = { Value = Beatmap.Value.BeatmapInfo.Version } + }, + } + }, + }, + }; } } } From fe31edfa26a126c1a3d55a1cad2c51e60ce3aaa7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:28:20 +0900 Subject: [PATCH 102/137] Add rudimentary saving logic --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 34 ++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 84e96a14e2..7ea810c514 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,10 +1,12 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -16,6 +18,12 @@ namespace osu.Game.Screens.Edit.Setup { public class SetupScreen : EditorScreen { + private FillFlowContainer flow; + private LabelledTextBox artistTextBox; + private LabelledTextBox titleTextBox; + private LabelledTextBox creatorTextBox; + private LabelledTextBox difficultyTextBox; + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -24,13 +32,14 @@ namespace osu.Game.Screens.Edit.Setup new Box { Colour = colours.Gray0, + Alpha = 0.4f, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(50), - Child = new FillFlowContainer + Child = flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -54,22 +63,22 @@ namespace osu.Game.Screens.Edit.Setup { Text = "Beatmap metadata" }, - new LabelledTextBox + artistTextBox = new LabelledTextBox { Label = "Artist", Current = { Value = Beatmap.Value.Metadata.Artist } }, - new LabelledTextBox + titleTextBox = new LabelledTextBox { Label = "Title", Current = { Value = Beatmap.Value.Metadata.Title } }, - new LabelledTextBox + creatorTextBox = new LabelledTextBox { Label = "Creator", Current = { Value = Beatmap.Value.Metadata.AuthorString } }, - new LabelledTextBox + difficultyTextBox = new LabelledTextBox { Label = "Difficulty Name", Current = { Value = Beatmap.Value.BeatmapInfo.Version } @@ -78,6 +87,21 @@ namespace osu.Game.Screens.Edit.Setup }, }, }; + + foreach (var item in flow.OfType()) + item.OnCommit += onCommit; + } + + private void onCommit(TextBox sender, bool newText) + { + if (!newText) return; + + // for now, update these on commit rather than making BeatmapMetadata bindables. + // after switching database engines we can reconsider if switching to bindables is a good direction. + Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value; + Beatmap.Value.Metadata.Title = titleTextBox.Current.Value; + Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value; + Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; } } } From c8281b17bdee45478edfbb71ecfec3541d1e1e7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:49:26 +0900 Subject: [PATCH 103/137] Remove editor screen fade (looks bad) --- osu.Game/Screens/Edit/EditorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index d42447ac4b..8b5f0aaa71 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit public void Exit() { - this.FadeOut(250).Expire(); + Expire(); } } } From b55b6e374699e06eed4c0178ad88eec195b4d972 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:50:44 +0900 Subject: [PATCH 104/137] Bring design somewhat in line with collections dialog --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 112 ++++++++++++--------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 7ea810c514..da8eb3a3b3 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -27,65 +27,77 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + Child = new Container { - new Box - { - Colour = colours.Gray0, - Alpha = 0.4f, - RelativeSizeAxes = Axes.Both, - }, - new OsuScrollContainer + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(50), + Child = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(50), - Child = flow = new FillFlowContainer + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(20), - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Box { - new Container + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Child = flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, - Height = 250, - Masking = true, - CornerRadius = 50, - Child = new BeatmapBackgroundSprite(Beatmap.Value) + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = 250, + Masking = true, + CornerRadius = 10, + Child = new BeatmapBackgroundSprite(Beatmap.Value) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, + }, + new OsuSpriteText + { + Text = "Beatmap metadata" + }, + artistTextBox = new LabelledTextBox + { + Label = "Artist", + Current = { Value = Beatmap.Value.Metadata.Artist } + }, + titleTextBox = new LabelledTextBox + { + Label = "Title", + Current = { Value = Beatmap.Value.Metadata.Title } + }, + creatorTextBox = new LabelledTextBox + { + Label = "Creator", + Current = { Value = Beatmap.Value.Metadata.AuthorString } + }, + difficultyTextBox = new LabelledTextBox + { + Label = "Difficulty Name", + Current = { Value = Beatmap.Value.BeatmapInfo.Version } + }, + } }, - new OsuSpriteText - { - Text = "Beatmap metadata" - }, - artistTextBox = new LabelledTextBox - { - Label = "Artist", - Current = { Value = Beatmap.Value.Metadata.Artist } - }, - titleTextBox = new LabelledTextBox - { - Label = "Title", - Current = { Value = Beatmap.Value.Metadata.Title } - }, - creatorTextBox = new LabelledTextBox - { - Label = "Creator", - Current = { Value = Beatmap.Value.Metadata.AuthorString } - }, - difficultyTextBox = new LabelledTextBox - { - Label = "Difficulty Name", - Current = { Value = Beatmap.Value.BeatmapInfo.Version } - }, - } - }, - }, + }, + } + } }; foreach (var item in flow.OfType()) From c38e7d796a577c477fbb844376dc6902667aa015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:51:31 +0900 Subject: [PATCH 105/137] Fix tab key not working --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 12 ++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 2cbe095d0b..290aba3468 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; @@ -32,6 +33,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => Component.Text = value; } + public Container TabbableContentContainer + { + set => Component.TabbableContentContainer = value; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index da8eb3a3b3..a2c8f19016 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -76,22 +76,26 @@ namespace osu.Game.Screens.Edit.Setup artistTextBox = new LabelledTextBox { Label = "Artist", - Current = { Value = Beatmap.Value.Metadata.Artist } + Current = { Value = Beatmap.Value.Metadata.Artist }, + TabbableContentContainer = this }, titleTextBox = new LabelledTextBox { Label = "Title", - Current = { Value = Beatmap.Value.Metadata.Title } + Current = { Value = Beatmap.Value.Metadata.Title }, + TabbableContentContainer = this }, creatorTextBox = new LabelledTextBox { Label = "Creator", - Current = { Value = Beatmap.Value.Metadata.AuthorString } + Current = { Value = Beatmap.Value.Metadata.AuthorString }, + TabbableContentContainer = this }, difficultyTextBox = new LabelledTextBox { Label = "Difficulty Name", - Current = { Value = Beatmap.Value.BeatmapInfo.Version } + Current = { Value = Beatmap.Value.BeatmapInfo.Version }, + TabbableContentContainer = this }, } }, From 2cd07b2d3c8d6e54e82c352b17870e48bcd0c060 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 12:48:11 +0900 Subject: [PATCH 106/137] Fix editor crash on saving more than once I'm fixing this in the simplest way possible as this kind of issue is specific to EF core, which may cease to exist quite soon. Turns out the re-retrieval of the beatmap set causes concurrency confusion and wasn't actually needed in my final iteration of the new beatmap logic. --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34bb578b2a..4496f3b330 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -231,7 +231,7 @@ namespace osu.Game.Beatmaps /// The beatmap content to write, null if to be omitted. public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { - var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); + var setInfo = info.BeatmapSet; using (var stream = new MemoryStream()) { From 8cd0bbe469436ce28999541fa9545c0193193cdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 14:31:23 +0900 Subject: [PATCH 107/137] Make BeatmapCollectionManager a component --- osu.Game/Collections/BeatmapCollectionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index ed41627d63..00ca660381 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -20,7 +20,7 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Collections { - public class BeatmapCollectionManager : CompositeDrawable + public class BeatmapCollectionManager : Component { /// /// Database version in stable-compatible YYYYMMDD format. From 5d9ce0df980bf7ea645c9362889f5ffa363c4750 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 14:44:04 +0900 Subject: [PATCH 108/137] Add remark about temporary nature of database format --- osu.Game/Collections/BeatmapCollectionManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 00ca660381..0e78c44024 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -20,6 +20,13 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Collections { + /// + /// Handles user-defined collections of beatmaps. + /// + /// + /// This is currently reading and writing from the osu-stable file format. This is a temporary arrangement until we refactor the + /// database backing the game. Going forward writing should be done in a similar way to other model stores. + /// public class BeatmapCollectionManager : Component { /// From 4ddf5f054ba3422d6e7c092ff8873e2a3815dea7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:31:08 +0900 Subject: [PATCH 109/137] Rename BeatmapCollectionManager -> CollectionManager --- .../Collections/IO/ImportCollectionsTest.cs | 16 ++++++++-------- .../TestSceneManageCollectionsDialog.cs | 4 ++-- .../Visual/SongSelect/TestSceneFilterControl.cs | 4 ++-- ...CollectionManager.cs => CollectionManager.cs} | 4 ++-- .../Collections/DrawableCollectionListItem.cs | 8 ++++---- osu.Game/Collections/ManageCollectionsDialog.cs | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- .../Sections/Maintenance/GeneralSettings.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- .../Screens/Select/CollectionFilterDropdown.cs | 2 +- 11 files changed, 25 insertions(+), 25 deletions(-) rename osu.Game/Collections/{BeatmapCollectionManager.cs => CollectionManager.cs} (99%) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 95013859f0..e2335b4d3c 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(new MemoryStream()); Assert.That(collectionManager.Collections.Count, Is.Zero); @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Collections.IO var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); using (var ms = new MemoryStream()) { @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); // Move first beatmap from second collection into the first. @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Collections.IO waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); waitForOrAssert(() => collectionManager.DatabaseLoaded, "Collection database did not load in a reasonable amount of time"); return osu; @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Collections.IO protected override void AddInternal(Drawable drawable) { // The beatmap must be imported just before the collection manager is loaded. - if (drawable is BeatmapCollectionManager && withBeatmap) + if (drawable is CollectionManager && withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); base.AddInternal(drawable); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 0c57c27911..54ab20af7f 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Collections private readonly Container content; private readonly DialogOverlay dialogOverlay; - private readonly BeatmapCollectionManager manager; + private readonly CollectionManager manager; private RulesetStore rulesets; private BeatmapManager beatmapManager; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Collections { base.Content.AddRange(new Drawable[] { - manager = new BeatmapCollectionManager(LocalStorage), + manager = new CollectionManager(LocalStorage), content = new Container { RelativeSizeAxes = Axes.Both }, dialogOverlay = new DialogOverlay() }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 5b0e244bbe..6012150513 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.SongSelect protected override Container Content => content; private readonly Container content; - private readonly BeatmapCollectionManager collectionManager; + private readonly CollectionManager collectionManager; private RulesetStore rulesets; private BeatmapManager beatmapManager; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.SongSelect { base.Content.AddRange(new Drawable[] { - collectionManager = new BeatmapCollectionManager(LocalStorage), + collectionManager = new CollectionManager(LocalStorage), content = new Container { RelativeSizeAxes = Axes.Both } }); } diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/CollectionManager.cs similarity index 99% rename from osu.Game/Collections/BeatmapCollectionManager.cs rename to osu.Game/Collections/CollectionManager.cs index 0e78c44024..8b91ab219f 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Collections /// This is currently reading and writing from the osu-stable file format. This is a temporary arrangement until we refactor the /// database backing the game. Going forward writing should be done in a similar way to other model stores. /// - public class BeatmapCollectionManager : Component + public class CollectionManager : Component { /// /// Database version in stable-compatible YYYYMMDD format. @@ -53,7 +53,7 @@ namespace osu.Game.Collections private readonly Storage storage; - public BeatmapCollectionManager(Storage storage) + public CollectionManager(Storage storage) { this.storage = storage; } diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a7075c3179..7d158f182f 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -28,7 +28,7 @@ namespace osu.Game.Collections private const float button_width = item_height * 0.75f; /// - /// Whether the currently exists inside the . + /// Whether the currently exists inside the . /// public IBindable IsCreated => isCreated; @@ -38,7 +38,7 @@ namespace osu.Game.Collections /// Creates a new . /// /// The . - /// Whether currently exists inside the . + /// Whether currently exists inside the . public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { @@ -63,7 +63,7 @@ namespace osu.Game.Collections private readonly BeatmapCollection collection; [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } private Container textBoxPaddingContainer; private ItemTextBox textBox; @@ -159,7 +159,7 @@ namespace osu.Game.Collections private DialogOverlay dialogOverlay { get; set; } [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } private readonly BeatmapCollection collection; diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index f6964191a1..cfde9d5550 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -20,7 +20,7 @@ namespace osu.Game.Collections private const double exit_duration = 200; [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } public ManageCollectionsDialog() { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d98d4e2123..d4741f9e69 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -57,7 +57,7 @@ namespace osu.Game protected BeatmapManager BeatmapManager; - protected BeatmapCollectionManager CollectionManager; + protected CollectionManager CollectionManager; protected ScoreManager ScoreManager; @@ -228,7 +228,7 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new BeatmapCollectionManager(Storage)); + dependencies.Cache(CollectionManager = new CollectionManager(Storage)); AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 30fd5921eb..83ee5e497a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, BeatmapCollectionManager collectionManager, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collectionManager, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index e9990ab078..1db73702bb 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapDifficultyManager difficultyManager { get; set; } [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fe700f12df..fd66315f67 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel private DialogOverlay dialogOverlay { get; set; } [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 6b9ae1b5c8..7270354e87 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(BeatmapCollectionManager collectionManager) + private void load(CollectionManager collectionManager) { collections.BindTo(collectionManager.Collections); collections.CollectionChanged += (_, __) => collectionsChanged(); From 0360f7d8456b0dd43f27713a55e81d9319d96ae0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:39:15 +0900 Subject: [PATCH 110/137] Move CollectionManager to OsuGame --- osu.Game/Collections/DrawableCollectionListItem.cs | 8 ++++---- osu.Game/Collections/ManageCollectionsDialog.cs | 5 +++-- osu.Game/OsuGame.cs | 9 ++++++--- osu.Game/OsuGameBase.cs | 6 ------ .../Select/Carousel/DrawableCarouselBeatmap.cs | 13 ++++++++----- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 13 ++++++++----- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 7d158f182f..988a3443c3 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -62,7 +62,7 @@ namespace osu.Game.Collections private readonly IBindable collectionName; private readonly BeatmapCollection collection; - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } private Container textBoxPaddingContainer; @@ -127,7 +127,7 @@ namespace osu.Game.Collections return; // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. - collectionManager.Collections.Add(collection); + collectionManager?.Collections.Add(collection); textBox.PlaceholderText = string.Empty; // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. @@ -158,7 +158,7 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } private readonly BeatmapCollection collection; @@ -231,7 +231,7 @@ namespace osu.Game.Collections return true; } - private void deleteCollection() => collectionManager.Collections.Remove(collection); + private void deleteCollection() => collectionManager?.Collections.Remove(collection); } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index cfde9d5550..680fec904f 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -19,7 +20,7 @@ namespace osu.Game.Collections private const double enter_duration = 500; private const double exit_duration = 200; - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } public ManageCollectionsDialog() @@ -100,7 +101,7 @@ namespace osu.Game.Collections new DrawableCollectionList { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = collectionManager.Collections } + Items = { BindTarget = collectionManager?.Collections ?? new BindableList() } } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0977f6c242..4a699dc82e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -538,9 +538,6 @@ namespace osu.Game ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.PresentImport = items => PresentScore(items.First()); - CollectionManager.PostNotification = n => notifications.Post(n); - CollectionManager.GetStableStorage = GetStorageForStableInstall; - Container logoContainer; BackButton.Receptor receptor; @@ -614,6 +611,12 @@ namespace osu.Game d.Origin = Anchor.TopRight; }), rightFloatingOverlayContent.Add, true); + loadComponentSingleFile(new CollectionManager(Storage) + { + PostNotification = n => notifications.Post(n), + GetStableStorage = GetStorageForStableInstall + }, Add, true); + loadComponentSingleFile(screenshotManager, Add); // dependency on notification overlay, dependent by settings overlay diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d4741f9e69..4bc8f4c527 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -26,7 +26,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; -using osu.Game.Collections; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -57,8 +56,6 @@ namespace osu.Game protected BeatmapManager BeatmapManager; - protected CollectionManager CollectionManager; - protected ScoreManager ScoreManager; protected SkinManager SkinManager; @@ -228,9 +225,6 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new CollectionManager(Storage)); - AddInternal(CollectionManager); - dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1db73702bb..10745fe3c1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapDifficultyManager difficultyManager { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] @@ -224,11 +224,14 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (collectionManager != null) + { + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } if (hideRequested != null) items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fd66315f67..3c8ac69dd2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] @@ -142,11 +142,14 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (collectionManager != null) + { + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); From 2d7e85f62203d5c017acd893d60d90fe22bc1325 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:40:45 +0900 Subject: [PATCH 111/137] Remove async load (now using loadComponentSingleFile) --- osu.Game/Collections/CollectionManager.cs | 40 +++++------------------ 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 8b91ab219f..a50ab5b07a 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -40,11 +40,6 @@ namespace osu.Game.Collections public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; - /// - /// Whether the user's database has finished loading. - /// - public bool DatabaseLoaded { get; private set; } - [Resolved] private GameHost host { get; set; } @@ -62,7 +57,12 @@ namespace osu.Game.Collections private void load() { Collections.CollectionChanged += collectionsChanged; - loadDatabase(); + + if (storage.Exists(database_name)) + { + using (var stream = storage.GetStream(database_name)) + importCollections(readCollections(stream)); + } } private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -91,17 +91,6 @@ namespace osu.Game.Collections backgroundSave(); } - private void loadDatabase() => Task.Run(async () => - { - if (storage.Exists(database_name)) - { - using (var stream = storage.GetStream(database_name)) - await import(stream); - } - - DatabaseLoaded = true; - }); - /// /// Set an endpoint for notifications to be posted to. /// @@ -149,14 +138,6 @@ namespace osu.Game.Collections PostNotification?.Invoke(notification); - await import(stream, notification); - } - - private async Task import(Stream stream, ProgressNotification notification = null) => await Task.Run(async () => - { - if (notification != null) - notification.Progress = 0; - var collection = readCollections(stream, notification); bool importCompleted = false; @@ -169,12 +150,9 @@ namespace osu.Game.Collections while (!IsDisposed && !importCompleted) await Task.Delay(10); - if (notification != null) - { - notification.CompletionText = $"Imported {collection.Count} collections"; - notification.State = ProgressNotificationState.Completed; - } - }); + notification.CompletionText = $"Imported {collection.Count} collections"; + notification.State = ProgressNotificationState.Completed; + } private void importCollections(List newCollections) { From b1b99e4d6f3d54bea94cd65b8b38f649a3037c25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:55:56 +0900 Subject: [PATCH 112/137] Fix tests --- .../Collections/IO/ImportCollectionsTest.cs | 75 ++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index e2335b4d3c..a79e0d0338 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Collections; using osu.Game.Tests.Resources; @@ -27,10 +26,9 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(new MemoryStream()); + await osu.CollectionManager.Import(new MemoryStream()); - Assert.That(collectionManager.Collections.Count, Is.Zero); + Assert.That(osu.CollectionManager.Collections.Count, Is.Zero); } finally { @@ -48,16 +46,15 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.Zero); + Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.Zero); - Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.Zero); + Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.Zero); } finally { @@ -75,16 +72,15 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1)); + Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1)); - Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12)); + Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12)); } finally { @@ -107,8 +103,6 @@ namespace osu.Game.Tests.Collections.IO var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); - using (var ms = new MemoryStream()) { using (var bw = new BinaryWriter(ms, Encoding.UTF8, true)) @@ -119,12 +113,12 @@ namespace osu.Game.Tests.Collections.IO ms.Seek(0, SeekOrigin.Begin); - await collectionManager.Import(ms); + await osu.CollectionManager.Import(ms); } Assert.That(host.UpdateThread.Running, Is.True); Assert.That(exceptionThrown, Is.False); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(0)); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0)); } finally { @@ -143,15 +137,14 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); // Move first beatmap from second collection into the first. - collectionManager.Collections[0].Beatmaps.Add(collectionManager.Collections[1].Beatmaps[0]); - collectionManager.Collections[1].Beatmaps.RemoveAt(0); + osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]); + osu.CollectionManager.Collections[1].Beatmaps.RemoveAt(0); // Rename the second collecction. - collectionManager.Collections[1].Name.Value = "Another"; + osu.CollectionManager.Collections[1].Name.Value = "Another"; } finally { @@ -165,15 +158,13 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2)); - - Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); - Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11)); + Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); + Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11)); } finally { @@ -182,7 +173,7 @@ namespace osu.Game.Tests.Collections.IO } } - private OsuGameBase loadOsu(GameHost host, bool withBeatmap = false) + private TestOsuGameBase loadOsu(GameHost host, bool withBeatmap = false) { var osu = new TestOsuGameBase(withBeatmap); @@ -192,9 +183,6 @@ namespace osu.Game.Tests.Collections.IO waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - var collectionManager = osu.Dependencies.Get(); - waitForOrAssert(() => collectionManager.DatabaseLoaded, "Collection database did not load in a reasonable amount of time"); - return osu; } @@ -210,6 +198,8 @@ namespace osu.Game.Tests.Collections.IO private class TestOsuGameBase : OsuGameBase { + public CollectionManager CollectionManager { get; private set; } + private readonly bool withBeatmap; public TestOsuGameBase(bool withBeatmap) @@ -217,13 +207,14 @@ namespace osu.Game.Tests.Collections.IO this.withBeatmap = withBeatmap; } - protected override void AddInternal(Drawable drawable) + [BackgroundDependencyLoader] + private void load() { - // The beatmap must be imported just before the collection manager is loaded. - if (drawable is CollectionManager && withBeatmap) + // Beatmap must be imported before the collection manager is loaded. + if (withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); - base.AddInternal(drawable); + AddInternal(CollectionManager = new CollectionManager(Storage)); } } } From 1a023d2c887ab72795c34e435cb22b4a130e2a6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 16:33:48 +0900 Subject: [PATCH 113/137] Fix a few more tests --- .../Sections/Maintenance/GeneralSettings.cs | 36 ++++++++++--------- .../Select/CollectionFilterDropdown.cs | 8 +++-- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 83ee5e497a..848ce381a9 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -27,8 +28,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton restoreButton; private TriangleButton undeleteButton; - [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collectionManager, DialogOverlay dialogOverlay) + [BackgroundDependencyLoader(permitNulls: true)] + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { @@ -108,28 +109,31 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (collectionManager.SupportsImportFromStable) + if (collectionManager != null) { - Add(importCollectionsButton = new SettingsButton + if (collectionManager.SupportsImportFromStable) { - Text = "Import collections from stable", + Add(importCollectionsButton = new SettingsButton + { + Text = "Import collections from stable", + Action = () => + { + importCollectionsButton.Enabled.Value = false; + collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + } + }); + } + + Add(new DangerousSettingsButton + { + Text = "Delete ALL collections", Action = () => { - importCollectionsButton.Enabled.Value = false; - collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll)); } }); } - Add(new DangerousSettingsButton - { - Text = "Delete ALL collections", - Action = () => - { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll)); - } - }); - AddRange(new Drawable[] { restoreButton = new SettingsButton diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 7270354e87..1e2a3d0aa7 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -37,10 +37,12 @@ namespace osu.Game.Screens.Select ItemSource = filters; } - [BackgroundDependencyLoader] - private void load(CollectionManager collectionManager) + [BackgroundDependencyLoader(permitNulls: true)] + private void load([CanBeNull] CollectionManager collectionManager) { - collections.BindTo(collectionManager.Collections); + if (collectionManager != null) + collections.BindTo(collectionManager.Collections); + collections.CollectionChanged += (_, __) => collectionsChanged(); collectionsChanged(); } From e271408fca532fd6e1dcd93fc68c380e1b19eab4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 16:51:53 +0900 Subject: [PATCH 114/137] Move max score calculation inside ScoreProcessor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 ++++---- osu.Game/Scoring/ScoreManager.cs | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7d138bd878..46994d4f18 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Scoring private readonly double accuracyPortion; private readonly double comboPortion; - private double maxHighestCombo; + private int maxHighestCombo; private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; @@ -204,10 +204,10 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxBaseScore, maxHighestCombo, baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore); + return GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); } - public double GetScore(ScoringMode mode, double maxBaseScore, double maxHighestCombo, double accuracyRatio, double comboRatio, double bonusScore) + public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore) { switch (mode) { @@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return bonusScore + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); + return bonusScore + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 634cca159a..5518c86910 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -154,10 +154,7 @@ namespace osu.Game.Scoring scoreProcessor.Mods.Value = score.Mods; - double maxBaseScore = 300 * beatmapMaxCombo; - double maxHighestCombo = beatmapMaxCombo; - - Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)); + Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, 0)); } } From 37a659b2af22ca0246cd9833ed1d21fe37726fa6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 17:04:02 +0900 Subject: [PATCH 115/137] Refactor/add xmldocs --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../Scores/TopScoreStatisticsSection.cs | 4 +-- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 +++++--- osu.Game/Scoring/ScoreManager.cs | 33 ++++++++++++++++--- .../ContractedPanelMiddleContent.cs | 2 +- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- 7 files changed, 45 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 846bebe347..dcd0cb435a 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 6bebd98eef..56866765b6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Current = scoreManager.GetTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 507c692eb1..2fd522dc9d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void load() { if (score != null) - totalScoreColumn.Current = scoreManager.GetTotalScoreString(score); + totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score); } private ScoreInfo score; @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores modsColumn.Mods = value.Mods; if (IsLoaded) - totalScoreColumn.Current = scoreManager.GetTotalScoreString(value); + totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(value); } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 46994d4f18..983f9a3abf 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -202,11 +202,17 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = getScore(Mode.Value); } - private double getScore(ScoringMode mode) - { - return GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); - } + private double getScore(ScoringMode mode) => GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); + /// + /// Computes the total score. + /// + /// The to compute the total score in. + /// The maximum combo achievable in the beatmap. + /// The accuracy percentage achieved by the player. + /// The proportion of achieved by the player. + /// Any bonus score to be added. + /// The total score. public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore) { switch (mode) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 5518c86910..5a6ef6945c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -86,15 +86,34 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public Bindable GetTotalScore(ScoreInfo score) + /// + /// Retrieves a bindable that represents the total score of a . + /// + /// + /// Responds to changes in the currently-selected . + /// + /// The to retrieve the bindable for. + /// The bindable containing the total score. + public Bindable GetBindableTotalScore(ScoreInfo score) { var bindable = new TotalScoreBindable(score, difficulties); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); return bindable; } - public Bindable GetTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetTotalScore(score)); + /// + /// Retrieves a bindable that represents the formatted total score string of a . + /// + /// + /// Responds to changes in the currently-selected . + /// + /// The to retrieve the bindable for. + /// The bindable containing the formatted total score string. + public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); + /// + /// Provides the total score of a . Responds to changes in the currently-selected . + /// private class TotalScoreBindable : Bindable { public readonly Bindable ScoringMode = new Bindable(); @@ -102,13 +121,16 @@ namespace osu.Game.Scoring private readonly ScoreInfo score; private readonly Func difficulties; + /// + /// Creates a new . + /// + /// The to provide the total score of. + /// A function to retrieve the . public TotalScoreBindable(ScoreInfo score, Func difficulties) { this.score = score; this.difficulties = difficulties; - Value = 0; - ScoringMode.BindValueChanged(onScoringModeChanged, true); } @@ -158,6 +180,9 @@ namespace osu.Game.Scoring } } + /// + /// Provides the total score of a as a formatted string. Responds to changes in the currently-selected . + /// private class TotalScoreStringBindable : Bindable { // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (need to hold a reference) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index b37b89e6c0..0b85eeafa8 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = scoreManager.GetTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), Spacing = new Vector2(-1, 0) }, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 3433410d3c..0033cd1f43 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Ranking.Expanded using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true)) { scoreCounter.FadeIn(); - scoreCounter.Current = scoreManager.GetTotalScore(score); + scoreCounter.Current = scoreManager.GetBindableTotalScore(score); double delay = 0; From 5cdc8d2e7b46c092031a7a0a7daf8f645cae4c13 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 17:37:11 +0900 Subject: [PATCH 116/137] Add cancellation support --- osu.Game/Scoring/ScoreManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 5a6ef6945c..619ca76598 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; +using System.Threading; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; @@ -135,9 +136,13 @@ namespace osu.Game.Scoring } private IBindable difficultyBindable; + private CancellationTokenSource difficultyCancellationSource; private void onScoringModeChanged(ValueChangedEvent mode) { + difficultyCancellationSource?.Cancel(); + difficultyCancellationSource = null; + if (score.Beatmap == null) { Value = score.TotalScore; @@ -156,7 +161,7 @@ namespace osu.Game.Scoring } // We can compute the max combo locally after the async beatmap difficulty computation. - difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods); + difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); } else From b1daca6cd33e19c71b91f90ad86f0247ebd4f628 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:05:44 +0900 Subject: [PATCH 117/137] Fix overlay sound effects playing when open requested while disabled --- .../Graphics/Containers/OsuFocusedOverlayContainer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 751ccc8f15..1d96e602d0 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -103,6 +103,8 @@ namespace osu.Game.Graphics.Containers { } + private bool playedPopInSound; + protected override void UpdateState(ValueChangedEvent state) { switch (state.NewValue) @@ -115,11 +117,18 @@ namespace osu.Game.Graphics.Containers } samplePopIn?.Play(); + playedPopInSound = true; + if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this); break; case Visibility.Hidden: - samplePopOut?.Play(); + if (playedPopInSound) + { + samplePopOut?.Play(); + playedPopInSound = false; + } + if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this); break; } From cdf3e206857186de7052061b5210642a421f6524 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:07:58 +0900 Subject: [PATCH 118/137] Add comment regarding feedback --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 1d96e602d0..41fd37a0d7 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -112,6 +112,7 @@ namespace osu.Game.Graphics.Containers case Visibility.Visible: if (OverlayActivationMode.Value == OverlayActivation.Disabled) { + // todo: visual/audible feedback that this operation could not complete. State.Value = Visibility.Hidden; return; } From c9f5005efd19270f54ab6834c5c3be301d38c11e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:35:25 +0900 Subject: [PATCH 119/137] Add icons for editor toolbox tools --- .../Edit/HitCircleCompositionTool.cs | 4 +++ .../Edit/SliderCompositionTool.cs | 4 +++ .../Edit/SpinnerCompositionTool.cs | 4 +++ .../TestSceneEditorComposeRadioButtons.cs | 3 ++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- .../Edit/Tools/HitObjectCompositionTool.cs | 4 +++ osu.Game/Rulesets/Edit/Tools/SelectTool.cs | 5 ++++ .../RadioButtons/DrawableRadioButton.cs | 27 +++++++------------ .../Components/RadioButtons/RadioButton.cs | 9 ++++++- 9 files changed, 42 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs index 9c94fe0e3d..5f7c8b77b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs index a377deb35f..596224e5c6 100644 --- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs index 0de0af8f8c..c5e90da3bd 100644 --- a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs index e4d7e025a8..0b52ae2b95 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Tests.Visual.Editing @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing { new RadioButton("Item 1", () => { }), new RadioButton("Item 2", () => { }), - new RadioButton("Item 3", () => { }), + new RadioButton("Item 3", () => { }, () => new SpriteIcon { Icon = FontAwesome.Regular.Angry }), new RadioButton("Item 4", () => { }), new RadioButton("Item 5", () => { }) } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f134db1ffe..955548fee9 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Edit toolboxCollection.Items = CompositionTools .Prepend(new SelectTool()) - .Select(t => new RadioButton(t.Name, () => toolSelected(t))) + .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) .ToList(); setSelectTool(); diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs index 0631031302..0a01ac4320 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; + namespace osu.Game.Rulesets.Edit.Tools { public abstract class HitObjectCompositionTool @@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Edit.Tools public abstract PlacementBlueprint CreatePlacementBlueprint(); + public virtual Drawable CreateIcon() => null; + public override string ToString() => Name; } } diff --git a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs index b96eeb0790..c050766b23 100644 --- a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; + namespace osu.Game.Rulesets.Edit.Tools { public class SelectTool : HitObjectCompositionTool @@ -10,6 +13,8 @@ namespace osu.Game.Rulesets.Edit.Tools { } + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.MousePointer }; + public override PlacementBlueprint CreatePlacementBlueprint() => null; } } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 7be91f4e8e..0cf7b83f3b 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -29,7 +28,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons private Color4 selectedBackgroundColour; private Color4 selectedBubbleColour; - private readonly Drawable bubble; + private Drawable icon; private readonly RadioButton button; public DrawableRadioButton(RadioButton button) @@ -40,19 +39,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Action = button.Select; RelativeSizeAxes = Axes.X; - - bubble = new CircularContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.5f), - X = 10, - Masking = true, - Blending = BlendingParameters.Additive, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; } [BackgroundDependencyLoader] @@ -73,7 +59,14 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Colour = Color4.Black.Opacity(0.5f) }; - Add(bubble); + Add(icon = (button.CreateIcon?.Invoke() ?? new Circle()).With(b => + { + b.Blending = BlendingParameters.Additive; + b.Anchor = Anchor.CentreLeft; + b.Origin = Anchor.CentreLeft; + b.Size = new Vector2(20); + b.X = 10; + })); } protected override void LoadComplete() @@ -96,7 +89,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons return; BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; - bubble.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; + icon.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; } protected override SpriteText CreateText() => new OsuSpriteText diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index b515d7c8bd..a7b0fb05e3 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { @@ -19,11 +20,17 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// public object Item; + /// + /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. + /// + public readonly Func CreateIcon; + private readonly Action action; - public RadioButton(object item, Action action) + public RadioButton(object item, Action action, Func createIcon = null) { Item = item; + CreateIcon = createIcon; this.action = action; Selected = new BindableBool(); } From a65f564e45b9cedee263ae7016d24b7c1f2928f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:39:55 +0900 Subject: [PATCH 120/137] Add icons for other ruleset editors --- osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index 295bf417c4..a5f10ed436 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Edit.Blueprints; @@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index 50b5f9a8fe..9f54152596 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs index bf77c76670..587a4efecb 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs index e877cf6240..3e97b4e322 100644 --- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs index a6191fcedc..918afde1dd 100644 --- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -1,6 +1,8 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); } } From d3957e6155de4871e74d41fc7efe91b6eda53d6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:48:02 +0900 Subject: [PATCH 121/137] Move title specification for settings groups to constructor Using an abstract property was awkward for this as it is being consumed in the underlying constructor but could not be dynamically set in time from a derived class. --- .../Gameplay/TestSceneReplaySettingsOverlay.cs | 5 ++++- .../Ladder/Components/LadderEditorSettings.cs | 7 +++++-- osu.Game/Rulesets/Edit/ToolboxGroup.cs | 3 +-- .../Play/PlayerSettings/CollectionSettings.cs | 5 ++++- .../Play/PlayerSettings/DiscussionSettings.cs | 5 ++++- .../Screens/Play/PlayerSettings/InputSettings.cs | 3 +-- .../Screens/Play/PlayerSettings/PlaybackSettings.cs | 3 +-- .../Play/PlayerSettings/PlayerSettingsGroup.cs | 13 ++++++------- .../Screens/Play/PlayerSettings/VisualSettings.cs | 3 +-- 9 files changed, 27 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs index cdfb3beb19..f8fab784cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs @@ -48,7 +48,10 @@ namespace osu.Game.Tests.Visual.Gameplay private class ExampleContainer : PlayerSettingsGroup { - protected override string Title => @"example"; + public ExampleContainer() + : base("example") + { + } } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index fa530ea2c4..b60eb814e5 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -20,8 +20,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { private const int padding = 10; - protected override string Title => @"ladder"; - private SettingsDropdown roundDropdown; private PlayerCheckbox losersCheckbox; private DateTextBox dateTimeBox; @@ -34,6 +32,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components [Resolved] private LadderInfo ladderInfo { get; set; } + public LadderEditorSettings() + : base("ladder") + { + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs index eabb834616..7e17d88e17 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -8,9 +8,8 @@ namespace osu.Game.Rulesets.Edit { public class ToolboxGroup : PlayerSettingsGroup { - protected override string Title => "toolbox"; - public ToolboxGroup() + : base("toolbox") { RelativeSizeAxes = Axes.X; Width = 1; diff --git a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs index d3570a8d2d..9e7f8e7394 100644 --- a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs @@ -10,7 +10,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class CollectionSettings : PlayerSettingsGroup { - protected override string Title => @"collections"; + public CollectionSettings() + : base("collections") + { + } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs index bb4eea47ca..ac040774ee 100644 --- a/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs @@ -10,7 +10,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class DiscussionSettings : PlayerSettingsGroup { - protected override string Title => @"discussions"; + public DiscussionSettings() + : base("discussions") + { + } [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 7a8696e27c..725a6e86bf 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -9,11 +9,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class InputSettings : PlayerSettingsGroup { - protected override string Title => "Input settings"; - private readonly PlayerCheckbox mouseButtonsCheckbox; public InputSettings() + : base("Input Settings") { Children = new Drawable[] { diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index c691d161ed..24ddc277cd 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -13,8 +13,6 @@ namespace osu.Game.Screens.Play.PlayerSettings { private const int padding = 10; - protected override string Title => @"playback"; - public readonly Bindable UserPlaybackRate = new BindableDouble(1) { Default = 1, @@ -28,6 +26,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly OsuSpriteText multiplierText; public PlaybackSettings() + : base("playback") { Children = new Drawable[] { diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 90424ec007..7928d41e3b 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -17,11 +17,6 @@ namespace osu.Game.Screens.Play.PlayerSettings { public abstract class PlayerSettingsGroup : Container { - /// - /// The title to be displayed in the header of this group. - /// - protected abstract string Title { get; } - private const float transition_duration = 250; private const int container_width = 270; private const int border_thickness = 2; @@ -58,7 +53,11 @@ namespace osu.Game.Screens.Play.PlayerSettings private Color4 expandedColour; - protected PlayerSettingsGroup() + /// + /// Create a new instance. + /// + /// The title to be displayed in the header of this group. + protected PlayerSettingsGroup(string title) { AutoSizeAxes = Axes.Y; Width = container_width; @@ -95,7 +94,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Text = Title.ToUpperInvariant(), + Text = title.ToUpperInvariant(), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Margin = new MarginPadding { Left = 10 }, }, diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index d6c66d0751..e06cf5c6d5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -10,8 +10,6 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class VisualSettings : PlayerSettingsGroup { - protected override string Title => "Visual settings"; - private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; private readonly PlayerCheckbox showStoryboardToggle; @@ -19,6 +17,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerCheckbox beatmapHitsoundsToggle; public VisualSettings() + : base("Visual Settings") { Children = new Drawable[] { From fb2aced3ac32d4e312913e410557885700c85933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:14:28 +0900 Subject: [PATCH 122/137] Add toggle for distance snap --- .../Edit/OsuHitObjectComposer.cs | 13 +++++++++++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 18 +++++++++++++++++- osu.Game/Rulesets/Edit/ToolboxGroup.cs | 4 ++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 37019a7a05..f87bd53ec3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -38,6 +39,13 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; + private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" }; + + protected override IEnumerable Toggles => new[] + { + distanceSnapToggle + }; + [BackgroundDependencyLoader] private void load() { @@ -45,6 +53,7 @@ namespace osu.Game.Rulesets.Osu.Edit EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); + distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid(); } protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) @@ -87,6 +96,10 @@ namespace osu.Game.Rulesets.Osu.Edit { distanceSnapGridContainer.Clear(); distanceSnapGridCache.Invalidate(); + distanceSnapGrid = null; + + if (!distanceSnapToggle.Value) + return; switch (BlueprintContainer.CurrentTool) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f134db1ffe..ee42cd9bae 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -13,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -94,7 +96,15 @@ namespace osu.Game.Rulesets.Edit Padding = new MarginPadding { Right = 10 }, Children = new Drawable[] { - new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } } + new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, + new ToolboxGroup("toggles") + { + ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox + { + Bindable = b, + LabelText = b?.Description ?? "unknown" + }) + } } }, new Container @@ -156,6 +166,12 @@ namespace osu.Game.Rulesets.Edit /// protected abstract IReadOnlyList CompositionTools { get; } + /// + /// A collection of toggles which will be displayed to the user. + /// The display name will be decided by . + /// + protected virtual IEnumerable Toggles => Enumerable.Empty(); + /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs index 7e17d88e17..22b2b05657 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Edit { public class ToolboxGroup : PlayerSettingsGroup { - public ToolboxGroup() - : base("toolbox") + public ToolboxGroup(string title) + : base(title) { RelativeSizeAxes = Axes.X; Width = 1; From d210e056294b8bd92e9828e6a7c30c3ae960d239 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:20:11 +0900 Subject: [PATCH 123/137] Add a touch of spacing between toolbox groups --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index ee42cd9bae..928cdd2ea0 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Edit Name = "Sidebar", RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 10 }, + Spacing = new Vector2(10), Children = new Drawable[] { new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, From ac0c4fcb8c2bfb162b820c9b03a128304fe31d0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:31:18 +0900 Subject: [PATCH 124/137] Add prompt to save beatmap on exiting editor --- osu.Game/Screens/Edit/Editor.cs | 57 ++++++++++++++------ osu.Game/Screens/Edit/PromptForSaveDialog.cs | 33 ++++++++++++ 2 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Screens/Edit/PromptForSaveDialog.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ac1f61c4fd..7e17225846 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -2,39 +2,40 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK.Graphics; -using osu.Framework.Screens; +using System.Collections.Generic; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Framework.Platform; -using osu.Framework.Timing; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Components; -using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Edit.Design; -using osuTK.Input; -using System.Collections.Generic; -using osu.Framework; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; +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.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Play; using osu.Game.Users; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Edit { @@ -54,6 +55,11 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved(canBeNull: true)] + private DialogOverlay dialogOverlay { get; set; } + + private bool exitConfirmed; + private Box bottomBackground; private Container screenContainer; @@ -346,12 +352,31 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { + if (!exitConfirmed && dialogOverlay != null) + { + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + return true; + } + Background.FadeColour(Color4.White, 500); resetTrack(); return base.OnExiting(next); } + private void confirmExitWithSave() + { + exitConfirmed = true; + saveBeatmap(); + this.Exit(); + } + + private void confirmExit() + { + exitConfirmed = true; + this.Exit(); + } + protected void Undo() => changeHandler.RestoreState(-1); protected void Redo() => changeHandler.RestoreState(1); diff --git a/osu.Game/Screens/Edit/PromptForSaveDialog.cs b/osu.Game/Screens/Edit/PromptForSaveDialog.cs new file mode 100644 index 0000000000..38d956557d --- /dev/null +++ b/osu.Game/Screens/Edit/PromptForSaveDialog.cs @@ -0,0 +1,33 @@ +// 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.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Edit +{ + public class PromptForSaveDialog : PopupDialog + { + public PromptForSaveDialog(Action exit, Action saveAndExit) + { + HeaderText = "Did you want to save your changes?"; + + Icon = FontAwesome.Regular.Save; + + Buttons = new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = @"Save my masterpiece!", + Action = saveAndExit + }, + new PopupDialogOkButton + { + Text = @"Forget all changes", + Action = exit + }, + }; + } + } +} From 6f067ff300910cf82a0cfac72d3578fd73c520d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:40:41 +0900 Subject: [PATCH 125/137] Only show confirmation if changes have been made since last save --- osu.Game/Screens/Edit/Editor.cs | 13 ++++++++++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7e17225846..58395e4848 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -60,6 +60,8 @@ namespace osu.Game.Screens.Edit private bool exitConfirmed; + private string lastSavedHash; + private Box bottomBackground; private Container screenContainer; @@ -124,6 +126,8 @@ namespace osu.Game.Screens.Edit changeHandler = new EditorChangeHandler(editorBeatmap); dependencies.CacheAs(changeHandler); + updateLastSavedHash(); + EditorMenuBar menuBar; OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; @@ -352,7 +356,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null) + if (!exitConfirmed && dialogOverlay != null && changeHandler.CurrentStateHash != lastSavedHash) { dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; @@ -447,6 +451,8 @@ namespace osu.Game.Screens.Edit // save the loaded beatmap's data stream. beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin); + + updateLastSavedHash(); } private void exportBeatmap() @@ -455,6 +461,11 @@ namespace osu.Game.Screens.Edit beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } + private void updateLastSavedHash() + { + lastSavedHash = changeHandler.CurrentStateHash; + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 927c823c64..aa0f89912a 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Text; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; @@ -24,6 +25,18 @@ namespace osu.Game.Screens.Edit private int currentState = -1; + /// + /// A SHA-2 hash representing the current visible editor state. + /// + public string CurrentStateHash + { + get + { + using (var stream = new MemoryStream(savedStates[currentState])) + return stream.ComputeSHA2Hash(); + } + } + private readonly EditorBeatmap editorBeatmap; private int bulkChangesStarted; private bool isRestoring; From 327179a81efbc9524be1a3a7d0ba1d54a3e46dff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:42:03 +0900 Subject: [PATCH 126/137] Expose unsaved changes state --- osu.Game/Screens/Edit/Editor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 58395e4848..34c69d09e0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -52,6 +52,8 @@ namespace osu.Game.Screens.Edit public override bool AllowRateAdjustments => false; + public bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -356,7 +358,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && changeHandler.CurrentStateHash != lastSavedHash) + if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges) { dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; From c6e72dabd372c35a589e2b5c23220e5478b43536 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:57:28 +0900 Subject: [PATCH 127/137] Add test coverage --- .../Editing/TestSceneEditorChangeStates.cs | 29 +++++++++++++++-- osu.Game/Screens/Edit/Editor.cs | 32 +++++++++---------- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index 293a6e6869..c8a32d966f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Editing protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected new TestEditor Editor => (TestEditor)base.Editor; + public override void SetUpSteps() { base.SetUpSteps(); @@ -35,6 +37,7 @@ namespace osu.Game.Tests.Visual.Editing addUndoSteps(); AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -47,6 +50,7 @@ namespace osu.Game.Tests.Visual.Editing addRedoSteps(); AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -64,9 +68,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); AddAssert("hitobject added", () => addedObject == expectedObject); + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); addUndoSteps(); AddAssert("hitobject removed", () => removedObject == expectedObject); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -94,6 +100,17 @@ namespace osu.Game.Tests.Visual.Editing addRedoSteps(); AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance) AddAssert("no hitobject removed", () => removedObject == null); + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); + } + + [Test] + public void TestAddObjectThenSaveHasNoUnsavedChanges() + { + AddStep("add hitobject", () => editorBeatmap.Add(new HitCircle { StartTime = 1000 })); + + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); + AddStep("save changes", () => Editor.Save()); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -120,6 +137,7 @@ namespace osu.Game.Tests.Visual.Editing addUndoSteps(); AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance) AddAssert("no hitobject removed", () => removedObject == null); + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); // 2 steps performed, 1 undone } [Test] @@ -148,19 +166,24 @@ namespace osu.Game.Tests.Visual.Editing addRedoSteps(); AddAssert("hitobject removed", () => removedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance after undo) AddAssert("no hitobject added", () => addedObject == null); + AddAssert("no changes", () => !Editor.HasUnsavedChanges); // end result is empty beatmap, matching original state } - private void addUndoSteps() => AddStep("undo", () => ((TestEditor)Editor).Undo()); + private void addUndoSteps() => AddStep("undo", () => Editor.Undo()); - private void addRedoSteps() => AddStep("redo", () => ((TestEditor)Editor).Redo()); + private void addRedoSteps() => AddStep("redo", () => Editor.Redo()); protected override Editor CreateEditor() => new TestEditor(); - private class TestEditor : Editor + protected class TestEditor : Editor { public new void Undo() => base.Undo(); public new void Redo() => base.Redo(); + + public new void Save() => base.Save(); + + public new bool HasUnsavedChanges => base.HasUnsavedChanges; } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 34c69d09e0..23eb704920 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Edit public override bool AllowRateAdjustments => false; - public bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Edit var fileMenuItems = new List { - new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap) + new EditorMenuItem("Save", MenuItemType.Standard, Save) }; if (RuntimeInfo.IsDesktop) @@ -249,6 +249,17 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + protected void Save() + { + // apply any set-level metadata changes. + beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); + + // save the loaded beatmap's data stream. + beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin); + + updateLastSavedHash(); + } + protected override void Update() { base.Update(); @@ -268,7 +279,7 @@ namespace osu.Game.Screens.Edit return true; case PlatformActionType.Save: - saveBeatmap(); + Save(); return true; } @@ -373,7 +384,7 @@ namespace osu.Game.Screens.Edit private void confirmExitWithSave() { exitConfirmed = true; - saveBeatmap(); + Save(); this.Exit(); } @@ -446,20 +457,9 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private void saveBeatmap() - { - // apply any set-level metadata changes. - beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); - - // save the loaded beatmap's data stream. - beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin); - - updateLastSavedHash(); - } - private void exportBeatmap() { - saveBeatmap(); + Save(); beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 9fc20fd0f2..a375a17bcf 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.Ruleset = ruleset; BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; + BeatmapInfo.BeatmapSet.Files = new List(); BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo { From 1803ecad8001db57c39b62fbab2ecacc07d7a9fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 20:00:38 +0900 Subject: [PATCH 128/137] Add cancel exit button --- osu.Game/Screens/Edit/PromptForSaveDialog.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/PromptForSaveDialog.cs b/osu.Game/Screens/Edit/PromptForSaveDialog.cs index 38d956557d..16504b47bd 100644 --- a/osu.Game/Screens/Edit/PromptForSaveDialog.cs +++ b/osu.Game/Screens/Edit/PromptForSaveDialog.cs @@ -27,6 +27,10 @@ namespace osu.Game.Screens.Edit Text = @"Forget all changes", Action = exit }, + new PopupDialogCancelButton + { + Text = @"Oops, continue editing", + }, }; } } From aeae009512aad6f33a7ba5b4a54b161460cc3ead Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 20:11:29 +0900 Subject: [PATCH 129/137] Disable online beatmap lookups in tests --- osu.Game/Beatmaps/BeatmapManager.cs | 12 ++++++++---- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34bb578b2a..d53f85c68d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -66,12 +66,14 @@ namespace osu.Game.Beatmaps private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; private readonly AudioManager audioManager; - private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly TextureStore textureStore; private readonly ITrackStore trackStore; + [CanBeNull] + private readonly BeatmapOnlineLookupQueue onlineLookupQueue; + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, - WorkingBeatmap defaultBeatmap = null) + WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) : base(storage, contextFactory, api, new BeatmapStore(contextFactory), host) { this.rulesets = rulesets; @@ -85,7 +87,8 @@ namespace osu.Game.Beatmaps beatmaps.ItemRemoved += removeWorkingCache; beatmaps.ItemUpdated += removeWorkingCache; - onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + if (performOnlineLookups) + onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); trackStore = audioManager.GetTrackStore(Files.Store); @@ -142,7 +145,8 @@ namespace osu.Game.Beatmaps bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); - await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken); + if (onlineLookupQueue != null) + await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4bc8f4c527..b61017f038 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -198,7 +198,7 @@ namespace osu.Game // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap, true)); // this should likely be moved to ArchiveModelManager when another case appers where it is necessary // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to From bbef7ff720c91dce79839d9d3182fbc712ec388b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 20:19:07 +0900 Subject: [PATCH 130/137] Fix leaderboard loading spinner disappearing too early --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index db0f835c67..084ba89f6e 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -56,13 +56,14 @@ namespace osu.Game.Online.Leaderboards scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - loading.Hide(); - showScoresDelegate?.Cancel(); showScoresCancellationSource?.Cancel(); if (scores == null || !scores.Any()) + { + loading.Hide(); return; + } // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; @@ -84,6 +85,7 @@ namespace osu.Game.Online.Leaderboards } scrollContainer.ScrollTo(0f, false); + loading.Hide(); }, (showScoresCancellationSource = new CancellationTokenSource()).Token)); } } From 12188ec3c931bc30e5bdb7e1c99f988027dbc33b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 20:49:59 +0900 Subject: [PATCH 131/137] Fix broken RollingCounter current value --- osu.Game/Graphics/UserInterface/RollingCounter.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index ece1b8e22c..91a557094d 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -9,16 +9,20 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { - public abstract class RollingCounter : Container + public abstract class RollingCounter : Container, IHasCurrentValue where T : struct, IEquatable { - /// - /// The current value. - /// - public Bindable Current = new Bindable(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } private SpriteText displayedCountSpriteText; From d7ca2cf1cca15da8fe476dfd4292d5bbd77cd167 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 22:01:09 +0900 Subject: [PATCH 132/137] Replace loaded check with better variation --- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 2fd522dc9d..3a842d0a43 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); modsColumn.Mods = value.Mods; - if (IsLoaded) + if (scoreManager != null) totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(value); } } From 43525614adefa151a20805225646b4421c9cbd3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 23:10:21 +0900 Subject: [PATCH 133/137] Store raw BeatmapCollection in filter control --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 6012150513..f89f22bf23 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -204,7 +204,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.CollectionName.Value == "1"); + AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1"); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 8e5655e514..3892e02a8f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select.Carousel } if (match) - match &= criteria.Collection?.ContainsBeatmap(Beatmap) ?? true; + match &= criteria.Collection?.Beatmaps.Contains(Beatmap) ?? true; Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 41ce0d65cd..9128160608 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - Collection = collectionDropdown?.Current.Value + Collection = collectionDropdown?.Current.Value.Collection }; if (!minimumStars.IsDefault) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index acab982945..66f164bca8 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -56,7 +57,7 @@ namespace osu.Game.Screens.Select /// The collection to filter beatmaps from. /// [CanBeNull] - public CollectionFilter Collection; + public BeatmapCollection Collection; public struct OptionalRange : IEquatable> where T : struct From 6b56c6e83ff29b2c375af41c69ed89c344d4c72a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 23:11:19 +0900 Subject: [PATCH 134/137] Rename to CollectionMenuItem --- .../SongSelect/TestSceneFilterControl.cs | 4 ++-- .../Select/CollectionFilterDropdown.cs | 24 +++++++++---------- ...lectionFilter.cs => CollectionMenuItem.cs} | 24 ++++++------------- 3 files changed, 21 insertions(+), 31 deletions(-) rename osu.Game/Screens/Select/{CollectionFilter.cs => CollectionMenuItem.cs} (60%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index f89f22bf23..23feb1466e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() - => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); + private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() + => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 1e2a3d0aa7..b08c3b167d 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -21,13 +21,13 @@ using osuTK; namespace osu.Game.Screens.Select { /// - /// A dropdown to select the to filter beatmaps using. + /// A dropdown to select the to filter beatmaps using. /// - public class CollectionFilterDropdown : OsuDropdown + public class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); - private readonly BindableList filters = new BindableList(); + private readonly BindableList filters = new BindableList(); [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } @@ -62,17 +62,17 @@ namespace osu.Game.Screens.Select var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); - filters.Add(new AllBeatmapCollectionFilter()); - filters.AddRange(collections.Select(c => new CollectionFilter(c))); - filters.Add(new ManageCollectionsFilter()); + filters.Add(new AllBeatmapsCollectionMenuItem()); + filters.AddRange(collections.Select(c => new CollectionMenuItem(c))); + filters.Add(new ManageCollectionsMenuItem()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } /// - /// Occurs when the selection has changed. + /// Occurs when the selection has changed. /// - private void filterChanged(ValueChangedEvent filter) + private void filterChanged(ValueChangedEvent filter) { // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. beatmaps.CollectionChanged -= filterBeatmapsChanged; @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. - if (filter.NewValue is ManageCollectionsFilter) + if (filter.NewValue is ManageCollectionsMenuItem) { Current.Value = filter.OldValue; manageCollectionsDialog?.Show(); @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(CollectionFilter item) => item.CollectionName.Value; + protected override string GenerateItemText(CollectionMenuItem item) => item.CollectionName.Value; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader { @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Select public class CollectionDropdownHeader : OsuDropdownHeader { - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); protected override string Label @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { [NotNull] - protected new CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; + protected new CollectionMenuItem Item => ((DropdownMenuItem)base.Item).Value; [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionMenuItem.cs similarity index 60% rename from osu.Game/Screens/Select/CollectionFilter.cs rename to osu.Game/Screens/Select/CollectionMenuItem.cs index 883019ab06..995651de19 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionMenuItem.cs @@ -1,10 +1,8 @@ // 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 JetBrains.Annotations; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Collections; namespace osu.Game.Screens.Select @@ -12,7 +10,7 @@ namespace osu.Game.Screens.Select /// /// A filter. /// - public class CollectionFilter + public class CollectionMenuItem { /// /// The collection to filter beatmaps from. @@ -28,35 +26,27 @@ namespace osu.Game.Screens.Select public readonly Bindable CollectionName; /// - /// Creates a new . + /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilter([CanBeNull] BeatmapCollection collection) + public CollectionMenuItem([CanBeNull] BeatmapCollection collection) { Collection = collection; CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } - - /// - /// Whether the collection contains a given beatmap. - /// - /// The beatmap to check. - /// Whether contains . - public virtual bool ContainsBeatmap(BeatmapInfo beatmap) - => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; } - public class AllBeatmapCollectionFilter : CollectionFilter + public class AllBeatmapsCollectionMenuItem : CollectionMenuItem { - public AllBeatmapCollectionFilter() + public AllBeatmapsCollectionMenuItem() : base(null) { } } - public class ManageCollectionsFilter : CollectionFilter + public class ManageCollectionsMenuItem : CollectionMenuItem { - public ManageCollectionsFilter() + public ManageCollectionsMenuItem() : base(null) { CollectionName.Value = "Manage collections..."; From df1537f2a03e55aad03e83223ed4656b146cb126 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Sep 2020 18:09:03 +0900 Subject: [PATCH 135/137] Update framework --- osu.Android.props | 2 +- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 62397ca028..a2686c380e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 20adbc1c02..88c855d768 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Statistics protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) { - if (pointGrid.Content.Length == 0) + if (pointGrid.Content.Count == 0) return; double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1de0633d1f..48582ae29d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7187b48907..0eed2fa911 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 18d96738a11133e3b82f7d8f3049e8d7d27d1aa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Sep 2020 19:37:40 +0900 Subject: [PATCH 136/137] Fix hard crash on deleting a collection with no collection selected --- osu.Game/Screens/Select/FilterControl.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 9128160608..3f1c88a1e3 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -189,7 +189,15 @@ namespace osu.Game.Screens.Select } }; - collectionDropdown.Current.ValueChanged += _ => updateCriteria(); + collectionDropdown.Current.ValueChanged += val => + { + if (val.NewValue == null) + // may be null briefly while menu is repopulated. + return; + + updateCriteria(); + }; + searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); From 447fd07b4ed4b7bf7f71862946ad975d8c07526f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 01:13:55 +0900 Subject: [PATCH 137/137] Fix maps with only bonus score having NaN scores --- .../Gameplay/TestSceneScoreProcessor.cs | 26 +++++++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index b0baf0385e..c9ab4fa489 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -28,6 +28,20 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); } + [Test] + public void TestOnlyBonusScore() + { + var beatmap = new Beatmap { HitObjects = { new TestBonusHitObject() } }; + + var scoreProcessor = new ScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + // Apply a judgement + scoreProcessor.ApplyResult(new JudgementResult(new TestBonusHitObject(), new TestBonusJudgement()) { Type = HitResult.Perfect }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(100)); + } + private class TestHitObject : HitObject { public override Judgement CreateJudgement() => new TestJudgement(); @@ -37,5 +51,17 @@ namespace osu.Game.Tests.Gameplay { protected override int NumericResultFor(HitResult result) => 100; } + + private class TestBonusHitObject : HitObject + { + public override Judgement CreateJudgement() => new TestBonusJudgement(); + } + + private class TestBonusJudgement : Judgement + { + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) => 100; + } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 983f9a3abf..6fa5a87c8e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -202,7 +202,13 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = getScore(Mode.Value); } - private double getScore(ScoringMode mode) => GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); + private double getScore(ScoringMode mode) + { + return GetScore(mode, maxHighestCombo, + maxBaseScore > 0 ? baseScore / maxBaseScore : 0, + maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 0, + bonusScore); + } /// /// Computes the total score.