diff --git a/global.json b/global.json index 233a040d18..a9a531f59c 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.52" + "Microsoft.Build.Traversal": "2.1.1" } } \ No newline at end of file diff --git a/osu.Android.props b/osu.Android.props index 1a76a24496..f62ba48953 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index f9d56dfa78..dfe3bf8af4 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index 18cc300ff9..f009c10a9c 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; @@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { Name = @"Fruit Count", Content = fruits.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), }, new BeatmapStatistic { Name = @"Juice Stream Count", Content = juiceStreams.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), }, new BeatmapStatistic { Name = @"Banana Shower Count", Content = bananaShowers.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners), } }; } diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index ed00ed0b4c..892f27d27f 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index dc24a344e9..d1d5adea75 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; @@ -41,14 +40,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps new BeatmapStatistic { Name = @"Note Count", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), Content = notes.ToString(), - Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Hold Note Count", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), Content = holdnotes.ToString(), - Icon = FontAwesome.Regular.Circle }, }; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index a69646507a..3d100e4b1c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Storyboards; using osuTK; -using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Rulesets.Osu.Tests { @@ -32,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audioManager { get; set; } - private TrackVirtualManual track; - protected override bool Autoplay => autoplay; private bool autoplay; @@ -44,11 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests private const double fade_in_modifier = -1200; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); - track = (TrackVirtualManual)working.Track; - return working; - } + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) @@ -72,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("enable autoplay", () => autoplay = true); base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; retrieveDrawableSlider(sliderIndex); @@ -97,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; retrieveDrawableSlider(sliderIndex); @@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => track.Seek(time)); + AddStep($"seek to {time}", () => MusicController.SeekTo(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 69857f8ef9..f7909071ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -25,7 +25,6 @@ using osu.Game.Scoring; using osu.Game.Storyboards; using osu.Game.Tests.Visual; using osuTK; -using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Rulesets.Osu.Tests { @@ -34,18 +33,12 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audioManager { get; set; } - private TrackVirtualManual track; - protected override bool Autoplay => true; protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer(); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); - track = (TrackVirtualManual)working.Track; - return working; - } + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); private DrawableSpinner drawableSpinner; private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single(); @@ -55,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests { base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First()); } @@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(0); - AddStep("adjust track rate", () => track.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); + AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); // autoplay replay frames use track time; // if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time. // therefore we need to apply the rate adjustment to the replay itself to change from track time to real time, @@ -230,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => track.Seek(time)); + AddStep($"seek to {time}", () => MusicController.SeekTo(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index f3837ea6b1..3639c3616f 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs index 491d82b89e..2d3cc3c103 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; @@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { Name = @"Circle Count", Content = circles.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), }, new BeatmapStatistic { Name = @"Slider Count", Content = sliders.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), }, new BeatmapStatistic { Name = @"Spinner Count", Content = spinners.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners), } }; } diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index e896606ee8..b59f3a4344 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index b595f43fbb..16a0726c8c 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; @@ -22,20 +21,20 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps new BeatmapStatistic { Name = @"Hit Count", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), Content = hits.ToString(), - Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Drumroll Count", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), Content = drumrolls.ToString(), - Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Swell Count", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners), Content = swells.ToString(), - Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 17d910036f..8b22309033 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Beatmaps.Formats protected override Texture GetBackground() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index cd3669f160..891537c4ad 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.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; using NUnit.Framework; using osu.Framework.Testing; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; @@ -19,7 +17,14 @@ namespace osu.Game.Tests.Gameplay { GameplayClockContainer gcc = null; - AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0))); + AddStep("create container", () => + { + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.LoadTrack(); + + Add(gcc = new GameplayClockContainer(working, 0)); + }); + AddStep("start track", () => gcc.Start()); AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); } diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index b30870d057..a690eb3b59 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -59,7 +59,10 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { - Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0)); + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.LoadTrack(); + + Add(gameplayContainer = new GameplayClockContainer(working, 0)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { @@ -103,7 +106,7 @@ namespace osu.Game.Tests.Gameplay Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); SelectedMods.Value = new[] { testedMod }; - Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0)); + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)); gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 4d3b73fb32..eff430ac25 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -26,6 +26,7 @@ namespace osu.Game.Tests.Skins { var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); + beatmap.LoadTrack(); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 79275d70a7..6fd5511e5a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Timing; @@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneCompletionCancellation : OsuPlayerTestScene { - private Track track; - [Resolved] private AudioManager audio { get; set; } @@ -34,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay base.SetUpSteps(); // Ensure track has actually running before attempting to seek - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); } [Test] @@ -73,13 +70,13 @@ namespace osu.Game.Tests.Visual.Gameplay private void complete() { - AddStep("seek to completion", () => track.Seek(5000)); + AddStep("seek to completion", () => Beatmap.Value.Track.Seek(5000)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); } private void cancel() { - AddStep("rewind to cancel", () => track.Seek(4000)); + AddStep("rewind to cancel", () => Beatmap.Value.Track.Seek(4000)); AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value); } @@ -91,11 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); - track = working.Track; - return working; - } + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 2a119f5199..73c6970482 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -5,7 +5,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -21,19 +20,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private AudioManager audioManager { get; set; } - private Track track; - - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); - track = working.Track; - return working; - } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [Test] public void TestNoJudgementsOnRewind() { - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); addSeekStep(3000); AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7)); @@ -46,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void addSeekStep(double time) { - AddStep($"seek to {time}", () => track.Seek(time)); + AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time)); // Allow a few frames of lenience AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 7ed7a116b4..841722a8f1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK; @@ -32,7 +30,10 @@ namespace osu.Game.Tests.Visual.Gameplay requestCount = 0; increment = skip_time; - Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty(), 0) + var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + working.LoadTrack(); + + Child = gameplayClockContainer = new GameplayClockContainer(working, 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 9f1492a25f..5a2b8d22fd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -22,19 +22,32 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneStoryboard : OsuTestScene { - private readonly Container storyboardContainer; + private Container storyboardContainer; private DrawableStoryboard storyboard; - [Cached] - private MusicController musicController = new MusicController(); + [Test] + public void TestStoryboard() + { + AddStep("Restart", restart); + AddToggleStep("Passing", passing => + { + if (storyboard != null) storyboard.Passing = passing; + }); + } - public TestSceneStoryboard() + [Test] + public void TestStoryboardMissingVideo() + { + AddStep("Load storyboard with missing video", loadStoryboardNoVideo); + } + + [BackgroundDependencyLoader] + private void load() { Clock = new FramedClock(); AddRange(new Drawable[] { - musicController, new Container { RelativeSizeAxes = Axes.Both, @@ -58,32 +71,11 @@ namespace osu.Game.Tests.Visual.Gameplay State = { Value = Visibility.Visible }, } }); + + Beatmap.BindValueChanged(beatmapChanged, true); } - [Test] - public void TestStoryboard() - { - AddStep("Restart", restart); - AddToggleStep("Passing", passing => - { - if (storyboard != null) storyboard.Passing = passing; - }); - } - - [Test] - public void TestStoryboardMissingVideo() - { - AddStep("Load storyboard with missing video", loadStoryboardNoVideo); - } - - [BackgroundDependencyLoader] - private void load() - { - Beatmap.ValueChanged += beatmapChanged; - } - - private void beatmapChanged(ValueChangedEvent e) - => loadStoryboard(e.NewValue); + private void beatmapChanged(ValueChangedEvent e) => loadStoryboard(e.NewValue); private void restart() { diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 8f20e38494..5f135febf4 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Audio.Track; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus @@ -15,11 +15,9 @@ namespace osu.Game.Tests.Visual.Menus public TestSceneIntroWelcome() { - AddUntilStep("wait for load", () => getTrack() != null); - - AddAssert("check if menu music loops", () => getTrack().Looping); + AddUntilStep("wait for load", () => MusicController.TrackLoaded); + AddAssert("correct track", () => Precision.AlmostEquals(MusicController.CurrentTrack.Length, 48000, 1)); + AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping); } - - private Track getTrack() => (IntroStack?.CurrentScreen as MainMenu)?.Track; } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs index d7f23f5cc0..4b22af38c5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs @@ -1,7 +1,6 @@ // 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.Overlays; @@ -11,14 +10,10 @@ namespace osu.Game.Tests.Visual.Menus { public class TestSceneSongTicker : OsuTestScene { - [Cached] - private MusicController musicController = new MusicController(); - public TestSceneSongTicker() { AddRange(new Drawable[] { - musicController, new SongTicker { Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index f819ae4682..2a4486812c 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Menus public class TestToolbar : Toolbar { - public new Bindable OverlayActivationMode => base.OverlayActivationMode; + public new Bindable OverlayActivationMode => base.OverlayActivationMode as Bindable; } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index 3d225aa0a9..faea32f90f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -162,6 +162,28 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); } + /// + /// Tests that the global mod instances are not retained by the rooms, as global mod instances are retained and re-used by the mod select overlay. + /// + [Test] + public void TestGlobalModInstancesNotRetained() + { + OsuModDoubleTime mod = null; + + AddStep("set dt mod and store", () => + { + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + + // Mod select overlay replaces our mod. + mod = (OsuModDoubleTime)SelectedMods.Value[0]; + }); + + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + + AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); + AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + } + private class TestMatchSongSelect : MatchSongSelect { public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8ccaca8630..73a833c15d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -46,7 +45,6 @@ namespace osu.Game.Tests.Visual.Navigation Player player = null; WorkingBeatmap beatmap() => Game.Beatmap.Value; - Track track() => beatmap().Track; PushAndConfirm(() => new TestSongSelect()); @@ -62,30 +60,27 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); AddUntilStep("wait for fail", () => player.HasFailed); - AddUntilStep("wait for track stop", () => !track().IsRunning); - AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime); + AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying); + AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); pushEscape(); - AddUntilStep("wait for track playing", () => track().IsRunning); - AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime); + AddUntilStep("wait for track playing", () => Game.MusicController.IsPlaying); + AddAssert("Ensure time wasn't reset to preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); } [Test] public void TestMenuMakesMusic() { - WorkingBeatmap beatmap() => Game.Beatmap.Value; - Track track() => beatmap().Track; - TestSongSelect songSelect = null; PushAndConfirm(() => songSelect = new TestSongSelect()); - AddUntilStep("wait for no track", () => track() is TrackVirtual); + AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice); AddStep("return to menu", () => songSelect.Exit()); - AddUntilStep("wait for track", () => !(track() is TrackVirtual) && track().IsRunning); + AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying); } [Test] @@ -140,12 +135,12 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded); AddStep("Seek close to end", () => { - Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000); - Game.Beatmap.Value.Track.Completed += () => trackCompleted = true; + Game.MusicController.SeekTo(Game.MusicController.CurrentTrack.Length - 1000); + Game.MusicController.CurrentTrack.Completed += () => trackCompleted = true; }); AddUntilStep("Track was completed", () => trackCompleted); - AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning); + AddUntilStep("Track was restarted", () => Game.MusicController.IsPlaying); } private void pushEscape() => diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs index e60adcee34..8f20bcdcc1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneFullscreenOverlay : OsuTestScene { - private FullscreenOverlay overlay; + private FullscreenOverlay overlay; protected override void LoadComplete() { @@ -38,10 +38,10 @@ namespace osu.Game.Tests.Visual.Online AddAssert("fire count 3", () => fireCount == 3); } - private class TestFullscreenOverlay : FullscreenOverlay + private class TestFullscreenOverlay : FullscreenOverlay { public TestFullscreenOverlay() - : base(OverlayColourScheme.Pink) + : base(OverlayColourScheme.Pink, null) { Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 103308d34d..9662bd65b4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby()); - AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null) + AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) { BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null } }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 48b718c04d..67cd720260 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -5,9 +5,9 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; @@ -53,53 +53,46 @@ namespace osu.Game.Tests.Visual.SongSelect private void showPersonalBestWithNullPosition() { - leaderboard.TopScore = new APILegacyUserTopScoreInfo + leaderboard.TopScore = new ScoreInfo { - Position = null, - Score = new APILegacyScoreInfo + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() }, + User = new User { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, - User = new User + Id = 6602580, + Username = @"waaiiru", + Country = new Country { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + FullName = @"Spain", + FlagName = @"ES", }, - } + }, }; } private void showPersonalBest() { - leaderboard.TopScore = new APILegacyUserTopScoreInfo + leaderboard.TopScore = new ScoreInfo { Position = 999, - Score = new APILegacyScoreInfo + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, - User = new User + Id = 6602580, + Username = @"waaiiru", + Country = new Country { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + FullName = @"Spain", + FlagName = @"ES", }, - } + }, }; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 0598324110..b8b8792b9b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -6,11 +6,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; namespace osu.Game.Tests.Visual.SongSelect @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.SongSelect public TestSceneUserTopScoreContainer() { - UserTopScoreContainer topScoreContainer; + UserTopScoreContainer topScoreContainer; Add(dialogOverlay = new DialogOverlay { @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect RelativeSizeAxes = Axes.Both, Colour = Color4.DarkGreen, }, - topScoreContainer = new UserTopScoreContainer + topScoreContainer = new UserTopScoreContainer(s => new LeaderboardScore(s, s.Position, false)) { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -52,69 +52,60 @@ namespace osu.Game.Tests.Visual.SongSelect var scores = new[] { - new APILegacyUserTopScoreInfo + new ScoreInfo { Position = 999, - Score = new APILegacyScoreInfo + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, - User = new User + Id = 6602580, + Username = @"waaiiru", + Country = new Country { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + FullName = @"Spain", + FlagName = @"ES", }, - } + }, }, - new APILegacyUserTopScoreInfo + new ScoreInfo { Position = 110000, - Score = new APILegacyScoreInfo + Rank = ScoreRank.X, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + User = new User { - Rank = ScoreRank.X, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - User = new User + Id = 4608074, + Username = @"Skycries", + Country = new Country { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + FullName = @"Brazil", + FlagName = @"BR", }, - } + }, }, - new APILegacyUserTopScoreInfo + new ScoreInfo { Position = 22333, - Score = new APILegacyScoreInfo + Rank = ScoreRank.S, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + User = new User { - Rank = ScoreRank.S, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - User = new User + Id = 1541390, + Username = @"Toukai", + Country = new Country { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + FullName = @"Canada", + FlagName = @"CA", }, - } + }, } }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index dd5ceec739..82b7e65c4f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.UserInterface { private readonly NowPlayingOverlay np; - [Cached] - private MusicController musicController = new MusicController(); - public TestSceneBeatSyncedContainer() { Clock = new FramedClock(); @@ -36,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddRange(new Drawable[] { - musicController, new BeatContainer { Anchor = Anchor.BottomCentre, @@ -71,6 +67,9 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Box flashLayer; + [Resolved] + private MusicController musicController { get; set; } + public BeatContainer() { RelativeSizeAxes = Axes.X; @@ -165,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength); + return (int)Math.Ceiling((musicController.CurrentTrack.Length - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index eb4750a597..e54292f7cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Framework.Testing; @@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, Audio, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); beatmap = beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result.Beatmaps[0]; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 532744a0fc..c14a1ddbf2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -2,6 +2,7 @@ // 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; @@ -11,6 +12,7 @@ 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 { @@ -20,8 +22,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private MusicController musicController = new MusicController(); - private WorkingBeatmap currentBeatmap; - private NowPlayingOverlay nowPlayingOverlay; private RulesetStore rulesets; @@ -76,16 +76,21 @@ namespace osu.Game.Tests.Visual.UserInterface } }).Wait(), 5); - AddStep(@"Next track", () => musicController.NextTrack()); - AddStep("Store track", () => currentBeatmap = Beatmap.Value); + 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", () => currentBeatmap.Track.CurrentTime > 5000); + 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", () => currentBeatmap.Track.CurrentTime < 5000); + AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack.CurrentTime < 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value); diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 90c91eb007..7dc5ce1d7f 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); - protected override Track GetTrack() => trackStore.Get(firstAudioFile); + protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile); private string firstAudioFile { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index d767973528..c692bcd5e4 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs new file mode 100644 index 0000000000..33165d385a --- /dev/null +++ b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs @@ -0,0 +1,41 @@ +// 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.Game.Tests.Visual; +using osu.Game.Tournament.Components; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tournament.Tests.Components +{ + public class TestSceneDateTextBox : OsuManualInputManagerTestScene + { + private DateTextBox textBox; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = textBox = new DateTextBox + { + Width = 0.3f + }; + }); + + [Test] + public void TestCommitWithoutSettingBindable() + { + AddStep("click textbox", () => + { + InputManager.MoveMouseTo(textBox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("unfocus", () => + { + InputManager.MoveMouseTo(Vector2.Zero); + InputManager.Click(MouseButton.Left); + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 95f5deb2cc..5d55196dcf 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index ee7e350970..aee5241e35 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -22,11 +22,12 @@ namespace osu.Game.Tournament.Components } // hold a reference to the provided bindable so we don't have to in every settings section. - private Bindable bindable; + private Bindable bindable = new Bindable(); public DateTextBox() { base.Bindable = new Bindable(); + ((OsuTextBox)Control).OnCommit = (sender, newText) => { try diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index f3eecf8afe..efec4cffdd 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -26,6 +26,8 @@ namespace osu.Game.Tournament.Screens.Editors [Cached] private LadderEditorInfo editorInfo = new LadderEditorInfo(); + private WarningBox rightClickMessage; + protected override bool DrawLoserPaths => true; [BackgroundDependencyLoader] @@ -37,6 +39,16 @@ namespace osu.Game.Tournament.Screens.Editors Origin = Anchor.TopRight, Margin = new MarginPadding(5) }); + + AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); + + LadderInfo.Matches.CollectionChanged += (_, __) => updateMessage(); + updateMessage(); + } + + private void updateMessage() + { + rightClickMessage.Alpha = LadderInfo.Matches.Count > 0 ? 0 : 1; } public void BeginJoin(TournamentMatch match, bool losers) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 307ee1c773..bbe4a53d8f 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -87,30 +87,7 @@ namespace osu.Game.Tournament }, } }, - heightWarning = new Container - { - Masking = true, - CornerRadius = 5, - Depth = float.MinValue, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Red, - RelativeSizeAxes = Axes.Both, - }, - new TournamentSpriteText - { - Text = "Please make the window wider", - Font = OsuFont.Torus.With(weight: FontWeight.Bold), - Colour = Color4.White, - Padding = new MarginPadding(20) - } - } - }, + heightWarning = new WarningBox("Please make the window wider"), new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/WarningBox.cs b/osu.Game.Tournament/WarningBox.cs new file mode 100644 index 0000000000..814482aea4 --- /dev/null +++ b/osu.Game.Tournament/WarningBox.cs @@ -0,0 +1,40 @@ +// 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.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Tournament +{ + internal class WarningBox : Container + { + public WarningBox(string text) + { + Masking = true; + CornerRadius = 5; + Depth = float.MinValue; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Box + { + Colour = Color4.Red, + RelativeSizeAxes = Axes.Both, + }, + new TournamentSpriteText + { + Text = text, + Font = OsuFont.Torus.With(weight: FontWeight.Bold), + Colour = Color4.White, + Padding = new MarginPadding(20) + } + }; + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a96af68714..35b9b90ce7 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -9,6 +9,7 @@ using System.Linq.Expressions; using System.Text; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -64,16 +65,16 @@ namespace osu.Game.Beatmaps private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; private readonly AudioManager audioManager; - private readonly GameHost host; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; + private readonly TextureStore textureStore; + private readonly ITrackStore trackStore; - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, api, new BeatmapStore(contextFactory), host) { this.rulesets = rulesets; this.audioManager = audioManager; - this.host = host; DefaultBeatmap = defaultBeatmap; @@ -84,6 +85,9 @@ namespace osu.Game.Beatmaps beatmaps.ItemUpdated += removeWorkingCache; onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + + textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); + trackStore = audioManager.GetTrackStore(Files.Store); } protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => @@ -220,7 +224,7 @@ namespace osu.Game.Beatmaps removeWorkingCache(info); } - private readonly WeakList workingCache = new WeakList(); + private readonly WeakList workingCache = new WeakList(); /// /// Retrieve a instance for the provided @@ -248,16 +252,13 @@ namespace osu.Game.Beatmaps lock (workingCache) { var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); + if (working != null) + return working; - if (working == null) - { - beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; + beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; - workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, - new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager)); - } + workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager)); - previous?.TransferTo(working); return working; } } @@ -461,7 +462,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; - protected override Track GetTrack() => null; + protected override Track GetBeatmapTrack() => null; } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 39c5ccab27..92199789ec 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -17,15 +17,18 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { - protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap + private class BeatmapManagerWorkingBeatmap : WorkingBeatmap { private readonly IResourceStore store; + private readonly TextureStore textureStore; + private readonly ITrackStore trackStore; - public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, BeatmapInfo beatmapInfo, AudioManager audioManager) + public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager) : base(beatmapInfo, audioManager) { this.store = store; this.textureStore = textureStore; + this.trackStore = trackStore; } protected override IBeatmap GetBeatmap() @@ -44,10 +47,6 @@ namespace osu.Game.Beatmaps private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - private TextureStore textureStore; - - private ITrackStore trackStore; - protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes. protected override Texture GetBackground() @@ -66,11 +65,11 @@ namespace osu.Game.Beatmaps } } - protected override Track GetTrack() + protected override Track GetBeatmapTrack() { try { - return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile)); + return trackStore.Get(getPathForFile(Metadata.AudioFile)); } catch (Exception e) { @@ -79,22 +78,6 @@ namespace osu.Game.Beatmaps } } - public override void RecycleTrack() - { - base.RecycleTrack(); - - trackStore?.Dispose(); - trackStore = null; - } - - public override void TransferTo(WorkingBeatmap other) - { - base.TransferTo(other); - - if (other is BeatmapManagerWorkingBeatmap owb && textureStore != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) - owb.textureStore = textureStore; - } - protected override Waveform GetWaveform() { try diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 0745ec5222..9d87a20d60 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -1,14 +1,31 @@ // 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; using osu.Framework.Graphics.Sprites; +using osuTK; namespace osu.Game.Beatmaps { public class BeatmapStatistic { - public IconUsage Icon; + [Obsolete("Use CreateIcon instead")] // can be removed 20210203 + public IconUsage Icon = FontAwesome.Regular.QuestionCircle; + + /// + /// A function to create the icon for display purposes. Use default icons available via whenever possible for conformity. + /// + public Func CreateIcon; + public string Content; public string Name; + + public BeatmapStatistic() + { +#pragma warning disable 618 + CreateIcon = () => new SpriteIcon { Icon = Icon, Scale = new Vector2(0.7f) }; +#pragma warning restore 618 + } } } diff --git a/osu.Game/Beatmaps/BeatmapStatisticIcon.cs b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs new file mode 100644 index 0000000000..181fb540df --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Beatmaps +{ + /// + /// A default implementation of an icon used to represent beatmap statistics. + /// + public class BeatmapStatisticIcon : Sprite + { + private readonly BeatmapStatisticsIconType iconType; + + public BeatmapStatisticIcon(BeatmapStatisticsIconType iconType) + { + this.iconType = iconType; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().Kebaberize()}"); + } + } + + public enum BeatmapStatisticsIconType + { + Accuracy, + ApproachRate, + Bpm, + Circles, + HpDrain, + Length, + OverallDifficulty, + Size, + Sliders, + Spinners, + } +} diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 8080e94075..af2a2ac250 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; @@ -19,7 +20,7 @@ namespace osu.Game.Beatmaps { private readonly TextureStore textures; - public DummyWorkingBeatmap(AudioManager audio, TextureStore textures) + public DummyWorkingBeatmap([NotNull] AudioManager audio, TextureStore textures) : base(new BeatmapInfo { Metadata = new BeatmapMetadata @@ -44,7 +45,7 @@ namespace osu.Game.Beatmaps protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4"); - protected override Track GetTrack() => GetVirtualTrack(); + protected override Track GetBeatmapTrack() => GetVirtualTrack(); private class DummyRulesetInfo : RulesetInfo { diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 31975157a0..bcd94d76fd 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -26,11 +26,6 @@ namespace osu.Game.Beatmaps /// Texture Background { get; } - /// - /// Retrieves the audio track for this . - /// - Track Track { get; } - /// /// Retrieves the for the of this . /// @@ -59,5 +54,18 @@ namespace osu.Game.Beatmaps /// The converted . /// If could not be converted to . IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null); + + /// + /// Load a new audio track instance for this beatmap. This should be called once before accessing . + /// The caller of this method is responsible for the lifetime of the track. + /// + /// + /// In a standard game context, the loading of the track is managed solely by MusicController, which will + /// automatically load the track of the current global IBindable WorkingBeatmap. + /// As such, this method should only be called in very special scenarios, such as external tests or apps which are + /// outside of the game context. + /// + /// A fresh track instance, which will also be available via . + Track LoadTrack(); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index b4bcf285b9..6a161e6e04 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; @@ -40,7 +41,6 @@ namespace osu.Game.Beatmaps BeatmapSetInfo = beatmapInfo.BeatmapSet; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - track = new RecyclableLazy(() => GetTrack() ?? GetVirtualTrack(1000)); background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); @@ -259,10 +259,39 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; - public virtual bool TrackLoaded => track.IsResultAvailable; - public Track Track => track.Value; - protected abstract Track GetTrack(); - private RecyclableLazy track; + private Track loadedTrack; + + [NotNull] + public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); + + /// + /// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap + /// across difficulties in the same beatmap set. + /// + /// The track to transfer. + public void TransferTrack([NotNull] Track track) => loadedTrack = track ?? throw new ArgumentNullException(nameof(track)); + + /// + /// Whether this beatmap's track has been loaded via . + /// + public virtual bool TrackLoaded => loadedTrack != null; + + /// + /// Get the loaded audio track instance. must have first been called. + /// This generally happens via MusicController when changing the global beatmap. + /// + public Track Track + { + get + { + if (!TrackLoaded) + throw new InvalidOperationException($"Cannot access {nameof(Track)} without first calling {nameof(LoadTrack)}."); + + return loadedTrack; + } + } + + protected abstract Track GetBeatmapTrack(); public bool WaveformLoaded => waveform.IsResultAvailable; public Waveform Waveform => waveform.Value; @@ -280,22 +309,6 @@ namespace osu.Game.Beatmaps protected virtual ISkin GetSkin() => new DefaultSkin(); private readonly RecyclableLazy skin; - /// - /// Transfer pieces of a beatmap to a new one, where possible, to save on loading. - /// - /// The new beatmap which is being switched to. - public virtual void TransferTo(WorkingBeatmap other) - { - if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) - other.track = track; - } - - /// - /// Eagerly dispose of the audio track associated with this (if any). - /// Accessing track again will load a fresh instance. - /// - public virtual void RecycleTrack() => track.Recycle(); - ~WorkingBeatmap() { total_count.Value--; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index df063f57d5..1c9cdc174a 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -47,7 +47,7 @@ namespace osu.Game.Graphics.Containers protected override void Update() { - Track track = null; + ITrack track = null; IBeatmap beatmap = null; double currentTrackTime = 0; diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 93ac69bdbf..751ccc8f15 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers [Resolved] private PreviewTrackManager previewTrackManager { get; set; } - protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); + protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); [BackgroundDependencyLoader(true)] private void load(AudioManager audio) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index d739f56828..f32f8e0c67 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -26,8 +26,11 @@ namespace osu.Game.Graphics.Containers { if (value == expandableHeader) return; - expandableHeader?.Expire(); + if (expandableHeader != null) + RemoveInternal(expandableHeader); + expandableHeader = value; + if (value == null) return; AddInternal(expandableHeader); diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index 75be9171b0..009639c1dc 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { @@ -22,5 +24,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"score")] public APILegacyScoreInfo Score; + + public ScoreInfo CreateScoreInfo(RulesetStore rulesets) + { + var score = Score.CreateScoreInfo(rulesets); + score.Position = Position; + return score; + } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs index 0bba6a93bd..bcc8721400 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs @@ -33,6 +33,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("user")] public User User { get; set; } + [JsonProperty("position")] + public int? Position { get; set; } + public ScoreInfo CreateScoreInfo() => new ScoreInfo { @@ -40,6 +43,7 @@ namespace osu.Game.Online.API.Requests.Responses PP = PP, TotalScore = TotalScore, User = User, + Position = Position }; } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 800029ceb9..db0f835c67 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -27,6 +27,7 @@ namespace osu.Game.Online.Leaderboards private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; + private readonly UserTopScoreContainer topScoreContainer; private FillFlowContainer scrollFlow; @@ -87,6 +88,20 @@ namespace osu.Game.Online.Leaderboards } } + public TScoreInfo TopScore + { + get => topScoreContainer.Score.Value; + set + { + topScoreContainer.Score.Value = value; + + if (value == null) + topScoreContainer.Hide(); + else + topScoreContainer.Show(); + } + } + protected virtual FillFlowContainer CreateScoreFlow() => new FillFlowContainer { @@ -198,8 +213,9 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, - } + }, }, }, }, @@ -367,5 +383,7 @@ namespace osu.Game.Online.Leaderboards } protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); + + protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); } } diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs similarity index 77% rename from osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs rename to osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index 8e10734454..ab4210251e 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -9,31 +9,29 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; -using osu.Game.Scoring; using osuTK; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { - public class UserTopScoreContainer : VisibilityContainer + public class UserTopScoreContainer : VisibilityContainer { private const int duration = 500; + public Bindable Score = new Bindable(); + private readonly Container scoreContainer; - - public Bindable Score = new Bindable(); - - public Action ScoreSelected; + private readonly Func createScoreDelegate; protected override bool StartHidden => true; [Resolved] private RulesetStore rulesets { get; set; } - public UserTopScoreContainer() + public UserTopScoreContainer(Func createScoreDelegate) { + this.createScoreDelegate = createScoreDelegate; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -72,7 +70,7 @@ namespace osu.Game.Screens.Select.Leaderboards private CancellationTokenSource loadScoreCancellation; - private void onScoreChanged(ValueChangedEvent score) + private void onScoreChanged(ValueChangedEvent score) { var newScore = score.NewValue; @@ -82,12 +80,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (newScore == null) return; - var scoreInfo = newScore.Score.CreateScoreInfo(rulesets); - - LoadComponentAsync(new LeaderboardScore(scoreInfo, newScore.Position, false) - { - Action = () => ScoreSelected?.Invoke(scoreInfo) - }, drawableScore => + LoadComponentAsync(createScoreDelegate(newScore), drawableScore => { scoreContainer.Child = drawableScore; drawableScore.FadeInFromZero(duration, Easing.OutQuint); diff --git a/osu.Game/Online/Multiplayer/APILeaderboard.cs b/osu.Game/Online/Multiplayer/APILeaderboard.cs new file mode 100644 index 0000000000..65863d6e0e --- /dev/null +++ b/osu.Game/Online/Multiplayer/APILeaderboard.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.Multiplayer +{ + public class APILeaderboard + { + [JsonProperty("leaderboard")] + public List Leaderboard; + + [JsonProperty("user_score")] + public APIUserScoreAggregate UserScore; + } +} diff --git a/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs b/osu.Game/Online/Multiplayer/GetRoomLeaderboardRequest.cs similarity index 65% rename from osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs rename to osu.Game/Online/Multiplayer/GetRoomLeaderboardRequest.cs index bc913030dd..37c21457bc 100644 --- a/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/GetRoomLeaderboardRequest.cs @@ -1,17 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.Multiplayer { - public class GetRoomScoresRequest : APIRequest> + public class GetRoomLeaderboardRequest : APIRequest { private readonly int roomId; - public GetRoomScoresRequest(int roomId) + public GetRoomLeaderboardRequest(int roomId) { this.roomId = roomId; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 053eb01dcd..164a40c6a5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -88,7 +88,10 @@ namespace osu.Game private IdleTracker idleTracker; - public readonly Bindable OverlayActivationMode = new Bindable(); + /// + /// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen. + /// + public readonly IBindable OverlayActivationMode = new Bindable(); protected OsuScreenStack ScreenStack; @@ -425,23 +428,7 @@ namespace osu.Game updateModDefaults(); - var newBeatmap = beatmap.NewValue; - - if (newBeatmap != null) - { - newBeatmap.Track.Completed += () => Scheduler.AddOnce(() => trackCompleted(newBeatmap)); - newBeatmap.BeginAsyncLoad(); - } - - void trackCompleted(WorkingBeatmap b) - { - // the source of track completion is the audio thread, so the beatmap may have changed before firing. - if (Beatmap.Value != b) - return; - - if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) - MusicController.NextTrack(); - } + beatmap.NewValue?.BeginAsyncLoad(); } private void modsChanged(ValueChangedEvent> mods) @@ -615,8 +602,6 @@ namespace osu.Game loadComponentSingleFile(new OnScreenDisplay(), Add, true); - loadComponentSingleFile(MusicController = new MusicController(), Add, true); - loadComponentSingleFile(notifications.With(d => { d.GetToolbarHeight = () => ToolbarOffset; @@ -713,9 +698,9 @@ namespace osu.Game float offset = 0; if (Settings.State.Value == Visibility.Visible) - offset += ToolbarButton.WIDTH / 2; + offset += Toolbar.HEIGHT / 2; if (notifications.State.Value == Visibility.Visible) - offset -= ToolbarButton.WIDTH / 2; + offset -= Toolbar.HEIGHT / 2; screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); } @@ -921,8 +906,6 @@ namespace osu.Game private ScalingContainer screenContainer; - protected MusicController MusicController { get; private set; } - protected override bool OnExiting() { if (ScreenStack.CurrentScreen is Loader) @@ -972,9 +955,12 @@ namespace osu.Game break; } + if (current is IOsuScreen currentOsuScreen) + OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); + if (newScreen is IOsuScreen newOsuScreen) { - OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; + OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 98f60d52d3..51b9b7278d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,6 +30,7 @@ using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; +using osu.Game.Overlays; using osu.Game.Resources; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -73,6 +74,8 @@ namespace osu.Game protected MenuCursorContainer MenuCursorContainer; + protected MusicController MusicController; + private Container content; protected override Container Content => content; @@ -238,16 +241,6 @@ namespace osu.Game Beatmap = new NonNullableBindable(defaultBeatmap); - // ScheduleAfterChildren is safety against something in the current frame accessing the previous beatmap's track - // and potentially causing a reload of it after just unloading. - // Note that the reason for this being added *has* been resolved, so it may be feasible to removed this if required. - Beatmap.BindValueChanged(b => ScheduleAfterChildren(() => - { - // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) - if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track) - b.OldValue.RecycleTrack(); - })); - dependencies.CacheAs>(Beatmap); dependencies.CacheAs(Beatmap); @@ -275,6 +268,9 @@ namespace osu.Game dependencies.Cache(previewTrackManager = new PreviewTrackManager()); Add(previewTrackManager); + AddInternal(MusicController = new MusicController()); + dependencies.CacheAs(MusicController); + Ruleset.BindValueChanged(onRulesetChanged); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index 1bab200fec..6a9a71210a 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -12,7 +12,8 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapListingTitle() { Title = "beatmap listing"; - IconTexture = "Icons/changelog"; + Description = "browse for new beatmaps"; + IconTexture = "Icons/Hexacons/beatmap"; } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 225a8a0578..144af91145 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -24,7 +24,7 @@ using osuTK; namespace osu.Game.Overlays { - public class BeatmapListingOverlay : FullscreenOverlay + public class BeatmapListingOverlay : FullscreenOverlay { [Resolved] private PreviewTrackManager previewTrackManager { get; set; } @@ -38,7 +38,7 @@ namespace osu.Game.Overlays private OverlayScrollContainer resultScrollContainer; public BeatmapListingOverlay() - : base(OverlayColourScheme.Blue) + : base(OverlayColourScheme.Blue, new BeatmapListingHeader()) { } @@ -65,7 +65,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - new BeatmapListingHeader(), + Header, filterControl = new BeatmapListingFilterControl { SearchStarted = onSearchStarted, diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index 4626589d81..6511b15fc8 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapHeaderTitle() { Title = "beatmap info"; - IconTexture = "Icons/changelog"; + IconTexture = "Icons/Hexacons/beatmap"; } } } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 3e23442023..bbec62a85a 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -19,12 +19,14 @@ using osuTK; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : FullscreenOverlay + public class BeatmapSetOverlay : FullscreenOverlay // we don't provide a standard header for now. { public const float X_PADDING = 40; public const float Y_PADDING = 25; public const float RIGHT_WIDTH = 275; - protected readonly Header Header; + + //todo: should be an OverlayHeader? or maybe not? + protected new readonly Header Header; [Resolved] private RulesetStore rulesets { get; set; } @@ -37,7 +39,7 @@ namespace osu.Game.Overlays private readonly Box background; public BeatmapSetOverlay() - : base(OverlayColourScheme.Blue) + : base(OverlayColourScheme.Blue, null) { OverlayScrollContainer scroll; Info info; diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 050bdea03a..f4be4328e7 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -115,7 +115,8 @@ namespace osu.Game.Overlays.Changelog public ChangelogHeaderTitle() { Title = "changelog"; - IconTexture = "Icons/changelog"; + Description = "track recent dev updates in the osu! ecosystem"; + IconTexture = "Icons/Hexacons/devtools"; } } } diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 726be9e194..c7e9a86fa4 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -21,12 +21,10 @@ using osu.Game.Overlays.Changelog; namespace osu.Game.Overlays { - public class ChangelogOverlay : FullscreenOverlay + public class ChangelogOverlay : FullscreenOverlay { public readonly Bindable Current = new Bindable(); - protected ChangelogHeader Header; - private Container content; private SampleChannel sampleBack; @@ -36,7 +34,7 @@ namespace osu.Game.Overlays protected List Streams; public ChangelogOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, new ChangelogHeader()) { } @@ -61,10 +59,11 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header = new ChangelogHeader + Header.With(h => { - ListingSelected = ShowListing, - }, + h.ListingSelected = ShowListing; + h.Build.BindTarget = Current; + }), content = new Container { RelativeSizeAxes = Axes.X, @@ -77,8 +76,6 @@ namespace osu.Game.Overlays sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); - Header.Build.BindTo(Current); - Current.BindValueChanged(e => { if (e.NewValue != null) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index b46ca6b040..be9ecc6746 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Chat.Selection { public class ChannelSelectionOverlay : WaveOverlayContainer { - public const float WIDTH_PADDING = 170; + public new const float WIDTH_PADDING = 170; private const float transition_duration = 500; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 5ba55f6d45..bcc2227be8 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osuTK; using osuTK.Graphics; @@ -25,8 +26,12 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays { - public class ChatOverlay : OsuFocusedOverlayContainer + public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { + public string IconTexture => "Icons/Hexacons/messaging"; + public string Title => "chat"; + public string Description => "Join the real-time discussion"; + private const float textbox_height = 60; private const float channel_selection_min_height = 0.3f; @@ -218,14 +223,13 @@ namespace osu.Game.Overlays Schedule(() => { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. - channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; - channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; + channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; + foreach (Channel channel in channelManager.JoinedChannels) ChannelTabControl.AddChannel(channel); - channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; - channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; - ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; + availableChannelsChanged(null, null); currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel.BindValueChanged(currentChannelChanged, true); @@ -384,34 +388,41 @@ namespace osu.Game.Overlays base.PopOut(); } - private void onChannelAddedToJoinedChannels(IEnumerable channels) + private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { - foreach (Channel channel in channels) - ChannelTabControl.AddChannel(channel); - } - - private void onChannelRemovedFromJoinedChannels(IEnumerable channels) - { - foreach (Channel channel in channels) + switch (args.Action) { - ChannelTabControl.RemoveChannel(channel); + case NotifyCollectionChangedAction.Add: + foreach (Channel channel in args.NewItems.Cast()) + ChannelTabControl.AddChannel(channel); + break; - var loaded = loadedChannels.Find(c => c.Channel == channel); + case NotifyCollectionChangedAction.Remove: + foreach (Channel channel in args.OldItems.Cast()) + { + ChannelTabControl.RemoveChannel(channel); - if (loaded != null) - { - loadedChannels.Remove(loaded); + var loaded = loadedChannels.Find(c => c.Channel == channel); - // Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared - // to ensure that the previous channel doesn't get updated after it's disposed - currentChannelContainer.Remove(loaded); - loaded.Dispose(); - } + if (loaded != null) + { + loadedChannels.Remove(loaded); + + // Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared + // to ensure that the previous channel doesn't get updated after it's disposed + currentChannelContainer.Remove(loaded); + loaded.Dispose(); + } + } + + break; } } - private void availableChannelsChanged(IEnumerable channels) - => ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) + { + ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + } protected override void Dispose(bool isDisposing) { @@ -420,10 +431,8 @@ namespace osu.Game.Overlays if (channelManager != null) { channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.ItemsAdded -= onChannelAddedToJoinedChannels; - channelManager.JoinedChannels.ItemsRemoved -= onChannelRemovedFromJoinedChannels; - channelManager.AvailableChannels.ItemsAdded -= availableChannelsChanged; - channelManager.AvailableChannels.ItemsRemoved -= availableChannelsChanged; + channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; + channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; } } diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 9ee679a866..36bf589877 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -12,7 +12,8 @@ namespace osu.Game.Overlays.Dashboard public DashboardTitle() { Title = "dashboard"; - IconTexture = "Icons/changelog"; + Description = "view your friends and other information"; + IconTexture = "Icons/Hexacons/social"; } } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index e3a4b0e152..8135b83a03 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -15,17 +15,21 @@ using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : FullscreenOverlay + public class DashboardOverlay : FullscreenOverlay { private CancellationTokenSource cancellationToken; private Container content; - private DashboardOverlayHeader header; private LoadingLayer loading; private OverlayScrollContainer scrollFlow; public DashboardOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, new DashboardOverlayHeader + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Depth = -float.MaxValue + }) { } @@ -50,12 +54,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new DashboardOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }, + Header, content = new Container { RelativeSizeAxes = Axes.X, @@ -72,7 +71,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - header.Current.BindValueChanged(onTabChanged); + Header.Current.BindValueChanged(onTabChanged); } private bool displayUpdateRequired = true; @@ -84,7 +83,7 @@ namespace osu.Game.Overlays // We don't want to create a new display on every call, only when exiting from fully closed state. if (displayUpdateRequired) { - header.Current.TriggerChange(); + Header.Current.TriggerChange(); displayUpdateRequired = false; } } @@ -136,7 +135,7 @@ namespace osu.Game.Overlays if (State.Value == Visibility.Hidden) return; - header.Current.TriggerChange(); + Header.Current.TriggerChange(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 3464ce6086..bd6b07c65f 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -12,16 +12,25 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent + public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent, INamedOverlayComponent + where T : OverlayHeader { + public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty; + public virtual string Title => Header?.Title.Title ?? string.Empty; + public virtual string Description => Header?.Title.Description ?? string.Empty; + + public T Header { get; } + [Resolved] protected IAPIProvider API { get; private set; } [Cached] protected readonly OverlayColourProvider ColourProvider; - protected FullscreenOverlay(OverlayColourScheme colourScheme) + protected FullscreenOverlay(OverlayColourScheme colourScheme, T header) { + Header = header; + ColourProvider = new OverlayColourProvider(colourScheme); RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Overlays/INamedOverlayComponent.cs b/osu.Game/Overlays/INamedOverlayComponent.cs new file mode 100644 index 0000000000..38fb8679a0 --- /dev/null +++ b/osu.Game/Overlays/INamedOverlayComponent.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays +{ + public interface INamedOverlayComponent + { + string IconTexture { get; } + + string Title { get; } + + string Description { get; } + } +} diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index b878aba489..b45d84049f 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Music { if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1)) { - beatmap.Value?.Track?.Seek(0); + beatmap.Value?.Track.Seek(0); return; } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a990f9a6ab..31bd80d6f3 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -4,10 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Track; 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; @@ -21,7 +25,7 @@ namespace osu.Game.Overlays /// /// Handles playback of the global music track. /// - public class MusicController : Component, IKeyBindingHandler + public class MusicController : CompositeDrawable, IKeyBindingHandler { [Resolved] private BeatmapManager beatmaps { get; set; } @@ -61,6 +65,9 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } + [NotNull] + public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); + private IBindable> managerUpdated; private IBindable> managerRemoved; @@ -73,12 +80,9 @@ namespace osu.Game.Overlays managerRemoved.BindValueChanged(beatmapRemoved); beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next())); - } - - protected override void LoadComplete() - { - base.LoadComplete(); + // Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now. + // They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load(). beatmap.BindValueChanged(beatmapChanged, true); mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } @@ -95,9 +99,14 @@ namespace osu.Game.Overlays } /// - /// Returns whether the current beatmap track is playing. + /// Returns whether the beatmap track is playing. /// - public bool IsPlaying => current?.Track.IsRunning ?? false; + public bool IsPlaying => CurrentTrack.IsRunning; + + /// + /// Returns whether the beatmap track is loaded. + /// + public bool TrackLoaded => CurrentTrack.TrackLoaded; private void beatmapUpdated(ValueChangedEvent> weakSet) { @@ -130,7 +139,7 @@ namespace osu.Game.Overlays seekDelegate = Schedule(() => { if (!beatmap.Disabled) - current?.Track.Seek(position); + CurrentTrack.Seek(position); }); } @@ -142,9 +151,7 @@ namespace osu.Game.Overlays { if (IsUserPaused) return; - var track = current?.Track; - - if (track == null || track is TrackVirtual) + if (CurrentTrack.IsDummyDevice) { if (beatmap.Disabled) return; @@ -163,17 +170,12 @@ namespace osu.Game.Overlays /// Whether the operation was successful. public bool Play(bool restart = false) { - var track = current?.Track; - IsUserPaused = false; - if (track == null) - return false; - if (restart) - track.Restart(); + CurrentTrack.Restart(); else if (!IsPlaying) - track.Start(); + CurrentTrack.Start(); return true; } @@ -183,11 +185,9 @@ namespace osu.Game.Overlays /// public void Stop() { - var track = current?.Track; - IsUserPaused = true; - if (track?.IsRunning == true) - track.Stop(); + if (CurrentTrack.IsRunning) + CurrentTrack.Stop(); } /// @@ -196,9 +196,7 @@ namespace osu.Game.Overlays /// Whether the operation was successful. public bool TogglePause() { - var track = current?.Track; - - if (track?.IsRunning == true) + if (CurrentTrack.IsRunning) Stop(); else Play(); @@ -220,7 +218,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return PreviousTrackResult.None; - var currentTrackPosition = current?.Track.CurrentTime; + var currentTrackPosition = CurrentTrack.CurrentTime; if (currentTrackPosition >= restart_cutoff_point) { @@ -234,9 +232,7 @@ namespace osu.Game.Overlays if (playable != null) { - if (beatmap is Bindable working) - working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - + changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value)); restartTrack(); return PreviousTrackResult.Previous; } @@ -260,9 +256,7 @@ namespace osu.Game.Overlays if (playable != null) { - if (beatmap is Bindable working) - working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - + changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value)); restartTrack(); return true; } @@ -274,21 +268,30 @@ namespace osu.Game.Overlays { // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => beatmap.Value.Track.Restart()); + Schedule(() => CurrentTrack.Restart()); } private WorkingBeatmap current; private TrackChangeDirection? queuedDirection; - private void beatmapChanged(ValueChangedEvent beatmap) + private void beatmapChanged(ValueChangedEvent beatmap) => changeBeatmap(beatmap.NewValue); + + private void changeBeatmap(WorkingBeatmap newWorking) { + // This method can potentially be triggered multiple times as it is eagerly fired in next() / prev() to ensure correct execution order + // (changeBeatmap must be called before consumers receive the bindable changed event, which is not the case when the local beatmap bindable is updated directly). + if (newWorking == current) + return; + + var lastWorking = current; + TrackChangeDirection direction = TrackChangeDirection.None; + bool audioEquals = newWorking?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false; + if (current != null) { - bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false; - if (audioEquals) direction = TrackChangeDirection.None; else if (queuedDirection.HasValue) @@ -300,18 +303,74 @@ namespace osu.Game.Overlays { // figure out the best direction based on order in playlist. var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); - var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count(); + var next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != newWorking.BeatmapSetInfo?.ID).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } } - current = beatmap.NewValue; + current = newWorking; + + if (!audioEquals || CurrentTrack.IsDummyDevice) + { + changeTrack(); + } + else + { + // transfer still valid track to new working beatmap + current.TransferTrack(lastWorking.Track); + } + TrackChanged?.Invoke(current, direction); ResetTrackAdjustments(); queuedDirection = null; + + // this will be a noop if coming from the beatmapChanged event. + // the exception is local operations like next/prev, where we want to complete loading the track before sending out a change. + if (beatmap.Value != current && beatmap is Bindable working) + working.Value = current; + } + + private void changeTrack() + { + var lastTrack = CurrentTrack; + + var queuedTrack = new DrawableTrack(current.LoadTrack()); + queuedTrack.Completed += () => onTrackCompleted(current); + + CurrentTrack = queuedTrack; + + // At this point we may potentially be in an async context from tests. This is extremely dangerous but we have to make do for now. + // CurrentTrack is immediately updated above for situations where a immediate knowledge about the new track is required, + // but the mutation of the hierarchy is scheduled to avoid exceptions. + Schedule(() => + { + lastTrack.VolumeTo(0, 500, Easing.Out).Expire(); + + if (queuedTrack == CurrentTrack) + { + AddInternal(queuedTrack); + queuedTrack.VolumeTo(0).Then().VolumeTo(1, 300, Easing.Out); + } + else + { + // If the track has changed since the call to changeTrack, it is safe to dispose the + // queued track rather than consume it. + queuedTrack.Dispose(); + } + }); + } + + private void onTrackCompleted(WorkingBeatmap workingBeatmap) + { + // the source of track completion is the audio thread, so the beatmap may have changed before firing. + if (current != workingBeatmap) + return; + + if (!CurrentTrack.Looping && !beatmap.Disabled) + NextTrack(); } private bool allowRateAdjustments; @@ -332,18 +391,20 @@ namespace osu.Game.Overlays } } + /// + /// Resets the speed adjustments currently applied on and applies the mod adjustments if is true. + /// + /// + /// Does not reset speed adjustments applied directly to the beatmap track. + /// public void ResetTrackAdjustments() { - var track = current?.Track; - if (track == null) - return; - - track.ResetSpeedAdjustments(); + CurrentTrack.ResetSpeedAdjustments(); if (allowRateAdjustments) { foreach (var mod in mods.Value.OfType()) - mod.ApplyToTrack(track); + mod.ApplyToTrack(CurrentTrack); } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index ddada2bdaf..63174128e7 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -57,7 +57,8 @@ namespace osu.Game.Overlays.News public NewsHeaderTitle() { Title = "news"; - IconTexture = "Icons/news"; + Description = "get up-to-date on community happenings"; + IconTexture = "Icons/Hexacons/news"; } } } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 09fb445b1f..c8c1db012f 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -13,17 +13,16 @@ using osu.Game.Overlays.News.Displays; namespace osu.Game.Overlays { - public class NewsOverlay : FullscreenOverlay + public class NewsOverlay : FullscreenOverlay { private readonly Bindable article = new Bindable(null); private Container content; private LoadingLayer loading; - private NewsHeader header; private OverlayScrollContainer scrollFlow; public NewsOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, new NewsHeader()) { } @@ -48,10 +47,10 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new NewsHeader + Header.With(h => { - ShowFrontPage = ShowFrontPage - }, + h.ShowFrontPage = ShowFrontPage; + }), content = new Container { RelativeSizeAxes = Axes.X, @@ -112,12 +111,12 @@ namespace osu.Game.Overlays if (e.NewValue == null) { - header.SetFrontPage(); + Header.SetFrontPage(); LoadDisplay(new FrontPageDisplay()); return; } - header.SetArticle(e.NewValue); + Header.SetArticle(e.NewValue); LoadDisplay(Empty()); } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 41160d10ec..b5714fbcae 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -16,8 +16,12 @@ using osu.Framework.Threading; namespace osu.Game.Overlays { - public class NotificationOverlay : OsuFocusedOverlayContainer + public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { + public string IconTexture => "Icons/Hexacons/notification"; + public string Title => "notifications"; + public string Description => "waiting for 'ya"; + private const float width = 320; public const float TRANSITION_LENGTH = 600; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index ebb4a96d14..55adf02a45 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -25,8 +25,12 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class NowPlayingOverlay : OsuFocusedOverlayContainer + public class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { + public string IconTexture => "Icons/Hexacons/music"; + public string Title => "now playing"; + public string Description => "manage the currently playing track"; + private const float player_height = 130; private const float transition_length = 800; private const float progress_height = 10; @@ -234,9 +238,9 @@ namespace osu.Game.Overlays pendingBeatmapSwitch = null; } - var track = beatmap.Value?.TrackLoaded ?? false ? beatmap.Value.Track : null; + var track = musicController.CurrentTrack; - if (track?.IsDummyDevice == false) + if (!track.IsDummyDevice) { progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index cc7f798c4a..fed1e57686 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -12,6 +12,8 @@ namespace osu.Game.Overlays { public abstract class OverlayHeader : Container { + public OverlayTitle Title { get; } + private float contentSidePadding; /// @@ -73,7 +75,7 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Children = new[] { - CreateTitle().With(title => + Title = CreateTitle().With(title => { title.Anchor = Anchor.CentreLeft; title.Origin = Anchor.CentreLeft; diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index e7415e6f74..b67d5db1a4 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -17,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { /// - /// which provides . Mostly used in . + /// which provides . Mostly used in . /// public class OverlayScrollContainer : OsuScrollContainer { diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs index 1c9567428c..17eeece1f8 100644 --- a/osu.Game/Overlays/OverlayTitle.cs +++ b/osu.Game/Overlays/OverlayTitle.cs @@ -12,19 +12,27 @@ using osuTK; namespace osu.Game.Overlays { - public abstract class OverlayTitle : CompositeDrawable + public abstract class OverlayTitle : CompositeDrawable, INamedOverlayComponent { - private readonly OsuSpriteText title; + private readonly OsuSpriteText titleText; private readonly Container icon; - protected string Title + private string title; + + public string Title { - set => title.Text = value; + get => title; + protected set => titleText.Text = title = value; } - protected string IconTexture + public string Description { get; protected set; } + + private string iconTexture; + + public string IconTexture { - set => icon.Child = new OverlayTitleIcon(value); + get => iconTexture; + protected set => icon.Child = new OverlayTitleIcon(iconTexture = value); } protected OverlayTitle() @@ -45,7 +53,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Horizontal = 5 }, // compensates for osu-web sprites having around 5px of whitespace on each side Size = new Vector2(30) }, - title = new OsuSpriteText + titleText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index 3e2c54c726..312271316a 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -9,7 +9,7 @@ using osu.Game.Online.API; namespace osu.Game.Overlays { /// - /// A subview containing online content, to be displayed inside a . + /// A subview containing online content, to be displayed inside a . /// /// /// Automatically performs a data fetch on load. diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 55474c9d3e..c947ef0781 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Profile public ProfileHeaderTitle() { Title = "player info"; - IconTexture = "Icons/profile"; + IconTexture = "Icons/Hexacons/profile"; } } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index e30c6f07a8..92e22f5873 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -30,7 +30,8 @@ namespace osu.Game.Overlays.Rankings public RankingsTitle() { Title = "ranking"; - IconTexture = "Icons/rankings"; + Description = "find out who's the best right now"; + IconTexture = "Icons/Hexacons/rankings"; } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 7b200d4226..ae6d49960a 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -17,17 +17,16 @@ using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : FullscreenOverlay + public class RankingsOverlay : FullscreenOverlay { - protected Bindable Country => header.Country; + protected Bindable Country => Header.Country; - protected Bindable Scope => header.Current; + protected Bindable Scope => Header.Current; private readonly OverlayScrollContainer scrollFlow; private readonly Container contentContainer; private readonly LoadingLayer loading; private readonly Box background; - private readonly RankingsOverlayHeader header; private APIRequest lastRequest; private CancellationTokenSource cancellationToken; @@ -36,7 +35,12 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } public RankingsOverlay() - : base(OverlayColourScheme.Green) + : base(OverlayColourScheme.Green, new RankingsOverlayHeader + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Depth = -float.MaxValue + }) { Children = new Drawable[] { @@ -55,12 +59,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new RankingsOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }, + Header, new Container { RelativeSizeAxes = Axes.X, @@ -97,7 +96,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - header.Ruleset.BindTo(ruleset); + Header.Ruleset.BindTo(ruleset); Country.BindValueChanged(_ => { diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index e0163b5b0c..1990674aa9 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.SearchableList /// /// The amount of padding added to content (does not affect background or tab control strip). /// - protected virtual float ContentHorizontalPadding => SearchableListOverlay.WIDTH_PADDING; + protected virtual float ContentHorizontalPadding => WaveOverlayContainer.WIDTH_PADDING; protected SearchableListFilterControl() { diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs deleted file mode 100644 index 66fedf0a56..0000000000 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ /dev/null @@ -1,82 +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 System; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; - -namespace osu.Game.Overlays.SearchableList -{ - public abstract class SearchableListHeader : Container - where T : struct, Enum - { - public readonly HeaderTabControl Tabs; - - protected abstract Color4 BackgroundColour { get; } - protected abstract T DefaultTab { get; } - protected abstract Drawable CreateHeaderText(); - protected abstract IconUsage Icon { get; } - - protected SearchableListHeader() - { - RelativeSizeAxes = Axes.X; - Height = 90; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING, Right = SearchableListOverlay.WIDTH_PADDING }, - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft, - Position = new Vector2(-35f, 5f), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), - Children = new[] - { - new SpriteIcon - { - Size = new Vector2(25), - Icon = Icon, - }, - CreateHeaderText(), - }, - }, - Tabs = new HeaderTabControl - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }, - }, - }, - }; - - Tabs.Current.Value = DefaultTab; - Tabs.Current.TriggerChange(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Tabs.StripColour = colours.Green; - } - } -} diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs deleted file mode 100644 index 4ab2de06b6..0000000000 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ /dev/null @@ -1,128 +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 System; -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Cursor; - -namespace osu.Game.Overlays.SearchableList -{ - public abstract class SearchableListOverlay : FullscreenOverlay - { - public const float WIDTH_PADDING = 80; - - protected SearchableListOverlay(OverlayColourScheme colourScheme) - : base(colourScheme) - { - } - } - - public abstract class SearchableListOverlay : SearchableListOverlay - where THeader : struct, Enum - where TTab : struct, Enum - where TCategory : struct, Enum - { - private readonly Container scrollContainer; - - protected readonly SearchableListHeader Header; - protected readonly SearchableListFilterControl Filter; - protected readonly FillFlowContainer ScrollFlow; - - protected abstract Color4 BackgroundColour { get; } - protected abstract Color4 TrianglesColourLight { get; } - protected abstract Color4 TrianglesColourDark { get; } - protected abstract SearchableListHeader CreateHeader(); - protected abstract SearchableListFilterControl CreateFilterControl(); - - protected SearchableListOverlay(OverlayColourScheme colourScheme) - : base(colourScheme) - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new[] - { - new Triangles - { - RelativeSizeAxes = Axes.Both, - TriangleScale = 5, - ColourLight = TrianglesColourLight, - ColourDark = TrianglesColourDark, - }, - }, - }, - scrollContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = ScrollFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10, Bottom = 50 }, - Direction = FillDirection.Vertical, - }, - }, - }, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header = CreateHeader(), - Filter = CreateFilterControl(), - }, - }, - }; - } - - protected override void Update() - { - base.Update(); - - scrollContainer.Padding = new MarginPadding { Top = Header.Height + Filter.Height }; - } - - protected override void OnFocus(FocusEvent e) - { - Filter.Search.TakeFocus(); - } - - protected override void PopIn() - { - base.PopIn(); - - Filter.Search.HoldFocus = true; - } - - protected override void PopOut() - { - base.PopOut(); - - Filter.Search.HoldFocus = false; - } - } -} diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index aca507f20a..e5cebd28e2 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -1,12 +1,14 @@ // 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.Graphics; using osu.Game.Overlays.Settings.Sections.Gameplay; using osu.Game.Rulesets; using System.Linq; using osu.Framework.Graphics.Sprites; +using osu.Framework.Logging; namespace osu.Game.Overlays.Settings.Sections { @@ -34,9 +36,17 @@ namespace osu.Game.Overlays.Settings.Sections { foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) { - SettingsSubsection section = ruleset.CreateSettings(); - if (section != null) - Add(section); + try + { + SettingsSubsection section = ruleset.CreateSettings(); + + if (section != null) + Add(section); + } + catch (Exception e) + { + Logger.Error(e, "Failed to load ruleset settings"); + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 00b7643332..4312b319c0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -163,8 +163,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything); }, true); - windowModes.ItemsAdded += _ => windowModesChanged(); - windowModes.ItemsRemoved += _ => windowModesChanged(); + windowModes.CollectionChanged += (sender, args) => windowModesChanged(); windowModesChanged(); } diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 358f94b659..031ecaae46 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -4,22 +4,21 @@ using System; using System.Linq; using osu.Framework; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Toolbar; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings { public class Sidebar : Container, IStateful { private readonly FillFlowContainer content; - public const float DEFAULT_WIDTH = ToolbarButton.WIDTH; + public const float DEFAULT_WIDTH = Toolbar.Toolbar.HEIGHT * 1.4f; public const int EXPANDED_WIDTH = 200; public event Action StateChanged; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index bb84de5d3a..e1bcdbbaf0 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -13,8 +13,12 @@ using osu.Framework.Bindables; namespace osu.Game.Overlays { - public class SettingsOverlay : SettingsPanel + public class SettingsOverlay : SettingsPanel, INamedOverlayComponent { + public string IconTexture => "Icons/Hexacons/settings"; + public string Title => "settings"; + public string Description => "change the way osu! behaves"; + protected override IEnumerable CreateSections() => new SettingsSection[] { new GeneralSection(), @@ -30,7 +34,7 @@ namespace osu.Game.Overlays private readonly List subPanels = new List(); - protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); + protected override Drawable CreateHeader() => new SettingsHeader(Title, Description); protected override Drawable CreateFooter() => new SettingsFooter(); public SettingsOverlay() diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 3bf9e85428..393e349bd0 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar private const double transition_time = 500; - protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); + protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. public override bool PropagateNonPositionalInputSubTree => true; diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs index cde305fffd..0363873326 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarBeatmapListingButton() { - SetIcon(OsuIcon.ChevronDownCircle); - TooltipMain = "Beatmap listing"; - TooltipSub = "Browse for new beatmaps"; - Hotkey = GlobalAction.ToggleDirect; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 0afc6642b2..49b9c62d85 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -25,8 +26,6 @@ namespace osu.Game.Overlays.Toolbar { public abstract class ToolbarButton : OsuClickableContainer, IKeyBindingHandler { - public const float WIDTH = Toolbar.HEIGHT * 1.4f; - protected GlobalAction? Hotkey { get; set; } public void SetIcon(Drawable icon) @@ -35,16 +34,14 @@ namespace osu.Game.Overlays.Toolbar IconContainer.Show(); } - public void SetIcon(IconUsage icon) => SetIcon(new SpriteIcon - { - Size = new Vector2(20), - Icon = icon - }); + [Resolved] + private TextureStore textures { get; set; } - public IconUsage Icon - { - set => SetIcon(value); - } + public void SetIcon(string texture) => + SetIcon(new Sprite + { + Texture = textures.Get(texture), + }); public string Text { @@ -82,7 +79,7 @@ namespace osu.Game.Overlays.Toolbar protected ToolbarButton() : base(HoverSampleSet.Loud) { - Width = WIDTH; + Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; Children = new Drawable[] @@ -116,7 +113,7 @@ namespace osu.Game.Overlays.Toolbar { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(20), + Size = new Vector2(26), Alpha = 0, }, DrawableText = new OsuSpriteText diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs index c88b418853..23f8b141b2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs @@ -2,19 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { public class ToolbarChangelogButton : ToolbarOverlayToggleButton { - public ToolbarChangelogButton() - { - SetIcon(FontAwesome.Solid.Bullhorn); - TooltipMain = "Changelog"; - TooltipSub = "Track recent dev updates in the osu! ecosystem"; - } - [BackgroundDependencyLoader(true)] private void load(ChangelogOverlay changelog) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index dee4be0c1f..f9a66ae7bb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarChatButton() { - SetIcon(FontAwesome.Solid.Comments); - TooltipMain = "Chat"; - TooltipSub = "Join the real-time discussion"; - Hotkey = GlobalAction.ToggleChat; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 4845c9a99f..76fbd40d66 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.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.Graphics.Sprites; +using osu.Framework.Allocation; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -10,11 +10,16 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Icon = FontAwesome.Solid.Home; - TooltipMain = "Home"; - TooltipSub = "Return to the main menu"; - + Width *= 1.4f; Hotkey = GlobalAction.Home; } + + [BackgroundDependencyLoader] + private void load() + { + TooltipMain = "home"; + TooltipSub = "return to the main menu"; + SetIcon("Icons/Hexacons/home"); + } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index f9aa2de497..0f5e8e5456 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -14,10 +13,6 @@ namespace osu.Game.Overlays.Toolbar public ToolbarMusicButton() { - Icon = FontAwesome.Solid.Music; - TooltipMain = "Now playing"; - TooltipSub = "Manage the currently playing track"; - Hotkey = GlobalAction.ToggleNowPlaying; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs index 106c67a041..0ba2935c80 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs @@ -2,19 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { public class ToolbarNewsButton : ToolbarOverlayToggleButton { - public ToolbarNewsButton() - { - Icon = FontAwesome.Solid.Newspaper; - TooltipMain = "News"; - TooltipSub = "Get up-to-date on community happenings"; - } - [BackgroundDependencyLoader(true)] private void load(NewsOverlay news) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index a699fd907f..79d0fc74c1 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -6,7 +6,6 @@ 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.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; @@ -25,10 +24,6 @@ namespace osu.Game.Overlays.Toolbar public ToolbarNotificationButton() { - Icon = FontAwesome.Solid.Bars; - TooltipMain = "Notifications"; - TooltipSub = "Waiting for 'ya"; - Hotkey = GlobalAction.ToggleNotifications; Add(countDisplay = new CountCircle diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 36387bb00d..0dea71cc08 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -32,6 +32,13 @@ namespace osu.Game.Overlays.Toolbar Action = stateContainer.ToggleVisibility; overlayState.BindTo(stateContainer.State); } + + if (stateContainer is INamedOverlayComponent named) + { + TooltipMain = named.Title; + TooltipSub = named.Description; + SetIcon(named.IconTexture); + } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs index c026ce99fe..22a01bcdb5 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs @@ -2,19 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { public class ToolbarRankingsButton : ToolbarOverlayToggleButton { - public ToolbarRankingsButton() - { - SetIcon(FontAwesome.Regular.ChartBar); - TooltipMain = "Ranking"; - TooltipSub = "Find out who's the best right now"; - } - [BackgroundDependencyLoader(true)] private void load(RankingsOverlay rankings) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 422bf00c6d..905d5b44c6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Toolbar }, ModeButtonLine = new Container { - Size = new Vector2(ToolbarButton.WIDTH, 3), + Size = new Vector2(Toolbar.HEIGHT, 3), Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, Masking = true, diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index a5194ea752..564fd65719 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Toolbar var rInstance = value.CreateInstance(); ruleset.TooltipMain = rInstance.Description; - ruleset.TooltipSub = $"Play some {rInstance.Description}"; + ruleset.TooltipSub = $"play some {rInstance.Description}"; ruleset.SetIcon(rInstance.CreateIcon()); } @@ -65,12 +65,6 @@ namespace osu.Game.Overlays.Toolbar Parent.Click(); return base.OnClick(e); } - - protected override void LoadComplete() - { - base.LoadComplete(); - IconContainer.Scale *= 1.4f; - } } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index ed2a23ec2a..c53f4a55d9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Icon = FontAwesome.Solid.Cog; - TooltipMain = "Settings"; - TooltipSub = "Change your settings"; - + Width *= 1.4f; Hotkey = GlobalAction.ToggleSettings; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 6faa58c559..e62c7bc807 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSocialButton() { - Icon = FontAwesome.Solid.Users; - TooltipMain = "Friends"; - TooltipSub = "Interact with those close to you"; - Hotkey = GlobalAction.ToggleSocial; } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index b4c8a2d3ca..2b316c0e34 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -17,19 +17,18 @@ using osuTK; namespace osu.Game.Overlays { - public class UserProfileOverlay : FullscreenOverlay + public class UserProfileOverlay : FullscreenOverlay { private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - protected ProfileHeader Header; private ProfileSectionsContainer sectionsContainer; private ProfileSectionTabControl tabs; public const float CONTENT_X_MARGIN = 70; public UserProfileOverlay() - : base(OverlayColourScheme.Pink) + : base(OverlayColourScheme.Pink, new ProfileHeader()) { } @@ -45,6 +44,9 @@ namespace osu.Game.Overlays if (user.Id == Header?.User.Value?.Id) return; + if (sectionsContainer != null) + sectionsContainer.ExpandableHeader = null; + userReq?.Cancel(); Clear(); lastSection = null; @@ -77,7 +79,7 @@ namespace osu.Game.Overlays Add(sectionsContainer = new ProfileSectionsContainer { - ExpandableHeader = Header = new ProfileHeader(), + ExpandableHeader = Header, FixedHeader = tabs, HeaderBackground = new Box { diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 5c87096dd4..d0fa9987d5 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays protected override bool BlockNonPositionalInput => true; protected override Container Content => Waves; + public const float WIDTH_PADDING = 80; + protected override bool StartHidden => true; protected WaveOverlayContainer() diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index 4d6d958e82..9b840cea08 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToTrack : IApplicableMod { - void ApplyToTrack(Track track); + void ApplyToTrack(ITrack track); } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index bd98e735e5..61ad7db706 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(Track track) + public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index ed8eb2fb66..4004953cd1 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(Track track) + public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 874384686f..fec21764b0 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public abstract BindableNumber SpeedChange { get; } - public virtual void ApplyToTrack(Track track) + public virtual void ApplyToTrack(ITrack track) { track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 839d97f04e..20c8d0f3e7 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - private Track track; + private ITrack track; protected ModTimeRamp() { @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(applyPitchAdjustment); } - public void ApplyToTrack(Track track) + public void ApplyToTrack(ITrack track) { this.track = track; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index ce2954f301..ed3d328330 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -26,6 +26,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorClock editorClock { get; set; } + /// + /// The timeline's scroll position in the last frame. + /// + private float lastScrollPosition; + + /// + /// The track time in the last frame. + /// + private double lastTrackTime; + + /// + /// Whether the user is currently dragging the timeline. + /// + private bool handlingDragInput; + + /// + /// Whether the track was playing before a user drag event. + /// + private bool trackWasPlaying; + + private Track track; + public Timeline() { ZoomDuration = 200; @@ -59,6 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform.Waveform = b.NewValue.Waveform; track = b.NewValue.Track; + // todo: i don't think this is safe, the track may not be loaded yet. if (track.Length > 0) { MaxZoom = getZoomLevelForVisibleMilliseconds(500); @@ -70,28 +93,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private float getZoomLevelForVisibleMilliseconds(double milliseconds) => Math.Max(1, (float)(track.Length / milliseconds)); - /// - /// The timeline's scroll position in the last frame. - /// - private float lastScrollPosition; - - /// - /// The track time in the last frame. - /// - private double lastTrackTime; - - /// - /// Whether the user is currently dragging the timeline. - /// - private bool handlingDragInput; - - /// - /// Whether the track was playing before a user drag event. - /// - private bool trackWasPlaying; - - private Track track; - protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index fc3dd4c105..57b7ce6940 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit protected override Texture GetBackground() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 5c59cfbfe8..c0c0bcead2 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -112,8 +112,7 @@ namespace osu.Game.Screens.Edit.Timing }; controlPoints = group.ControlPoints.GetBoundCopy(); - controlPoints.ItemsAdded += _ => createChildren(); - controlPoints.ItemsRemoved += _ => createChildren(); + controlPoints.CollectionChanged += (_, __) => createChildren(); createChildren(); } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index a08a660e7e..8c40c8e721 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -124,8 +124,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - controlGroups.ItemsAdded += _ => createContent(); - controlGroups.ItemsRemoved += _ => createContent(); + controlGroups.CollectionChanged += (sender, args) => createContent(); createContent(); } diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 761f842c22..e19037c2c4 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -39,9 +39,9 @@ namespace osu.Game.Screens bool HideOverlaysOnEnter { get; } /// - /// Whether overlays should be able to be opened once this screen is entered or resumed. + /// Whether overlays should be able to be opened when this screen is current. /// - OverlayActivation InitialOverlayActivationMode { get; } + IBindable OverlayActivationMode { get; } /// /// The amount of parallax to be applied while this screen is displayed. diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 5ba7a8ddc3..4becdd58cd 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -270,9 +270,6 @@ namespace osu.Game.Screens.Menu ButtonSystemState lastState = state; state = value; - if (game != null) - game.OverlayActivationMode.Value = state == ButtonSystemState.Exit ? OverlayActivation.Disabled : OverlayActivation.All; - updateLogoState(lastState); Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}"); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 5f91aaad15..1df5c503d6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; +using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; using osuTK; @@ -43,9 +44,7 @@ namespace osu.Game.Screens.Menu private WorkingBeatmap initialBeatmap; - protected Track Track => initialBeatmap?.Track; - - private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); + protected ITrack Track { get; private set; } private const int exit_delay = 3000; @@ -60,8 +59,12 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } + [Resolved] + private MusicController musicController { get; set; } + /// /// Whether the is provided by osu! resources, rather than a user beatmap. + /// Only valid during or after . /// protected bool UsingThemedIntro { get; private set; } @@ -111,7 +114,6 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - UsingThemedIntro = !(Track is TrackVirtual); } return UsingThemedIntro; @@ -123,17 +125,35 @@ namespace osu.Game.Screens.Menu this.FadeIn(300); double fadeOutTime = exit_delay; + + var track = musicController.CurrentTrack; + + // ensure the track doesn't change or loop as we are exiting. + track.Looping = false; + Beatmap.Disabled = true; + // we also handle the exit transition. if (MenuVoice.Value) + { seeya.Play(); + + // if playing the outro voice, we have more time to have fun with the background track. + // initially fade to almost silent then ramp out over the remaining time. + const double initial_fade = 200; + track + .VolumeTo(0.03f, initial_fade).Then() + .VolumeTo(0, fadeOutTime - initial_fade, Easing.In); + } else + { fadeOutTime = 500; - audio.AddAdjustment(AdjustableProperty.Volume, exitingVolumeFade); - this.TransformBindableTo(exitingVolumeFade, 0, fadeOutTime).OnComplete(_ => this.Exit()); + // if outro voice is turned off, just do a simple fade out. + track.VolumeTo(0, fadeOutTime, Easing.Out); + } //don't want to fade out completely else we will stop running updates. - Game.FadeTo(0.01f, fadeOutTime); + Game.FadeTo(0.01f, fadeOutTime).OnComplete(_ => this.Exit()); base.OnResuming(last); } @@ -164,6 +184,11 @@ namespace osu.Game.Screens.Menu if (!resuming) { beatmap.Value = initialBeatmap; + Track = initialBeatmap.Track; + UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice; + + // ensure the track starts at maximum volume + musicController.CurrentTrack.FinishTransforms(); logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index a9ef20436f..a96fddb5ad 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - if (MenuVoice.Value && !UsingThemedIntro) + if (MenuVoice.Value) welcome = audio.Samples.Get(@"Intro/welcome"); } @@ -64,7 +64,8 @@ namespace osu.Game.Screens.Menu }, t => { AddInternal(t); - welcome?.Play(); + if (!UsingThemedIntro) + welcome?.Play(); StartTrack(); }); diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index bf42e36e8c..e81646456f 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -39,8 +39,6 @@ namespace osu.Game.Screens.Menu welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); - - Track.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -49,6 +47,8 @@ namespace osu.Game.Screens.Menu if (!resuming) { + Track.Looping = true; + LoadComponentAsync(new WelcomeIntroSequence { RelativeSizeAxes = Axes.Both diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 57252d557e..2e883142d6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -5,7 +5,6 @@ using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; @@ -46,8 +45,8 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } - [Resolved(canBeNull: true)] - private MusicController music { get; set; } + [Resolved] + private MusicController musicController { get; set; } [Resolved(canBeNull: true)] private LoginOverlay login { get; set; } @@ -62,8 +61,6 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; - internal Track Track { get; private set; } - private Bindable holdDelay; private Bindable loginDisplayed; @@ -177,15 +174,14 @@ namespace osu.Game.Screens.Menu base.OnEntering(last); buttons.FadeInFromZero(500); - Track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; - if (last is IntroScreen && Track != null) + if (last is IntroScreen && musicController.TrackLoaded) { - if (!Track.IsRunning) + if (!musicController.CurrentTrack.IsRunning) { - Track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * Track.Length); - Track.Start(); + musicController.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.CurrentTrack.Length); + musicController.CurrentTrack.Start(); } } @@ -260,7 +256,7 @@ namespace osu.Game.Screens.Menu // we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - music.EnsurePlayingSomething(); + musicController.EnsurePlayingSomething(); } public override bool OnExiting(IScreen next) @@ -280,6 +276,7 @@ namespace osu.Game.Screens.Menu } buttons.State = ButtonSystemState.Exit; + OverlayActivationMode.Value = OverlayActivation.Disabled; songTicker.Hide(); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f5e4b078da..4515ee8ed0 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -17,6 +17,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -46,7 +47,6 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBeat; private readonly Container colourAndTriangles; - private readonly Triangles triangles; /// @@ -319,6 +319,9 @@ namespace osu.Game.Screens.Menu intro.Delay(length + fade).FadeOut(); } + [Resolved] + private MusicController musicController { get; set; } + protected override void Update() { base.Update(); @@ -327,9 +330,9 @@ namespace osu.Game.Screens.Menu const float velocity_adjust_cutoff = 0.98f; const float paused_velocity = 0.5f; - if (Beatmap.Value.Track.IsRunning) + if (musicController.CurrentTrack.IsRunning) { - var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0; + var maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); if (maxAmplitude > velocity_adjust_cutoff) diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 653cb3791a..cd8695286b 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.SearchableList; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Multi Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, + Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { title = new MultiHeaderTitle diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 447c99039a..321d7b0a19 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -53,8 +54,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components protected override void LoadComplete() { - rooms.ItemsAdded += addRooms; - rooms.ItemsRemoved += removeRooms; + rooms.CollectionChanged += roomsChanged; roomManager.RoomsUpdated += updateSorting; rooms.BindTo(roomManager.Rooms); @@ -82,6 +82,20 @@ namespace osu.Game.Screens.Multi.Lounge.Components }); } + private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + addRooms(args.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + removeRooms(args.OldItems.Cast()); + break; + } + } + private void addRooms(IEnumerable rooms) { foreach (var room in rooms) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index ff7d56a95b..a1e99c83b2 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Overlays; -using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; @@ -32,7 +31,7 @@ namespace osu.Game.Screens.Multi.Lounge [Resolved] private Bindable selectedRoom { get; set; } - [Resolved(canBeNull: true)] + [Resolved] private MusicController music { get; set; } private bool joiningRoom; @@ -102,8 +101,8 @@ namespace osu.Game.Screens.Multi.Lounge content.Padding = new MarginPadding { Top = Filter.DrawHeight, - Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING, - Right = SearchableListOverlay.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING, + Left = WaveOverlayContainer.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING, + Right = WaveOverlayContainer.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING, }; } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 1afbf5c32a..f2409d64e7 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi.Match.Components { public class MatchLeaderboard : Leaderboard { - public Action> ScoresLoaded; - [Resolved(typeof(Room), nameof(Room.RoomID))] private Bindable roomId { get; set; } @@ -39,18 +37,20 @@ namespace osu.Game.Screens.Multi.Match.Components if (roomId.Value == null) return null; - var req = new GetRoomScoresRequest(roomId.Value ?? 0); + var req = new GetRoomLeaderboardRequest(roomId.Value ?? 0); req.Success += r => { - scoresCallback?.Invoke(r); - ScoresLoaded?.Invoke(r); + scoresCallback?.Invoke(r.Leaderboard); + TopScore = r.UserScore; }; return req; } protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); + + protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position, false); } public enum MatchLeaderboardScope diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs index 73a40d9579..1fabdbb86a 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Multi.Match.Components { private readonly APIUserScoreAggregate score; - public MatchLeaderboardScore(APIUserScoreAggregate score, int rank) - : base(score.CreateScoreInfo(), rank) + public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool allowHighlight = true) + : base(score.CreateScoreInfo(), rank, allowHighlight) { this.score = score; } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 49a0fc434b..caefc194b1 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; -using osu.Game.Overlays.SearchableList; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Multi.Match.Components { new Container { - Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new Drawable[] diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 7c2d5cf85d..0d2adeb27c 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.Multi.Match new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 240), + new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120), } }, null diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 872a1cd39a..a44d14fb5c 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -44,9 +44,13 @@ namespace osu.Game.Screens public virtual bool HideOverlaysOnEnter => false; /// - /// Whether overlays should be able to be opened once this screen is entered or resumed. + /// The initial overlay activation mode to use when this screen is entered for the first time. /// - public virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + + protected readonly Bindable OverlayActivationMode; + + IBindable IOsuScreen.OverlayActivationMode => OverlayActivationMode; public virtual bool CursorVisible => true; @@ -138,6 +142,8 @@ namespace osu.Game.Screens { Anchor = Anchor.Centre; Origin = Anchor.Centre; + + OverlayActivationMode = new Bindable(InitialOverlayActivationMode); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0653373c91..7a9cb3dddd 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using osu.Framework; @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play { @@ -25,12 +24,9 @@ namespace osu.Game.Screens.Play public class GameplayClockContainer : Container { private readonly WorkingBeatmap beatmap; - private readonly IReadOnlyList mods; - /// - /// The 's track. - /// - private Track track; + [NotNull] + private ITrack track; public readonly BindableBool IsPaused = new BindableBool(); @@ -63,17 +59,16 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime) { this.beatmap = beatmap; - this.mods = mods; this.gameplayStartTime = gameplayStartTime; + track = beatmap.Track; + firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; RelativeSizeAxes = Axes.Both; - track = beatmap.Track; - adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. @@ -123,13 +118,10 @@ namespace osu.Game.Screens.Play public void Restart() { - // The Reset() call below causes speed adjustments to be reset in an async context, leading to deadlocks. - // The deadlock can be prevented by resetting the track synchronously before entering the async context. - track.ResetSpeedAdjustments(); - Task.Run(() => { - track.Reset(); + track.Seek(0); + track.Stop(); Schedule(() => { @@ -195,16 +187,13 @@ namespace osu.Game.Screens.Play } /// - /// Changes the backing clock to avoid using the originally provided beatmap's track. + /// Changes the backing clock to avoid using the originally provided track. /// public void StopUsingBeatmapClock() { - if (track != beatmap.Track) - return; - removeSourceClockAdjustments(); - track = new TrackVirtual(beatmap.Track.Length); + track = new TrackVirtual(track.Length); adjustableClock.ChangeSource(track); } @@ -220,33 +209,29 @@ namespace osu.Game.Screens.Play private void updateRate() { - if (track == null) return; - - speedAdjustmentsApplied = true; - track.ResetSpeedAdjustments(); + if (speedAdjustmentsApplied) + return; track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - foreach (var mod in mods.OfType()) - mod.ApplyToTrack(track); + speedAdjustmentsApplied = true; } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - removeSourceClockAdjustments(); - track = null; } private void removeSourceClockAdjustments() { - if (speedAdjustmentsApplied) - { - track.ResetSpeedAdjustments(); - speedAdjustmentsApplied = false; - } + if (!speedAdjustmentsApplied) return; + + track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + + speedAdjustmentsApplied = false; } private class HardwareCorrectionOffsetClock : FramedOffsetClock diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..e78b2a694f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -50,7 +50,10 @@ namespace osu.Game.Screens.Play public override bool HideOverlaysOnEnter => true; - public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + + // We are managing our own adjustments (see OnEntering/OnExiting). + public override bool AllowRateAdjustments => false; /// /// Whether gameplay should pause when the game window focus is lost. @@ -77,6 +80,9 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private MusicController musicController { get; set; } + private SampleChannel sampleRestart; public BreakOverlay BreakOverlay; @@ -178,7 +184,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); @@ -627,6 +633,13 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); + + // Our mods are local copies of the global mods so they need to be re-applied to the track. + // This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack. + // Todo: In the future, player will receive in a track and will probably not have to worry about this... + musicController.ResetTrackAdjustments(); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToTrack(musicController.CurrentTrack); } public override void OnSuspending(IScreen next) @@ -660,6 +673,8 @@ namespace osu.Game.Screens.Play // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); + musicController.ResetTrackAdjustments(); + fadeOut(); return base.OnExiting(next); } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index ad977c70b5..2a3eb8c67a 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -318,14 +318,14 @@ namespace osu.Game.Screens.Select labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", - Icon = FontAwesome.Regular.Clock, + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", - Icon = FontAwesome.Regular.Circle, + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), Content = getBPMRange(b), })); @@ -418,10 +418,18 @@ namespace osu.Game.Screens.Select Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), Colour = Color4Extensions.FromHex(@"f7dd55"), - Icon = statistic.Icon, + Icon = FontAwesome.Regular.Circle, + Size = new Vector2(0.8f) }, + statistic.CreateIcon().With(i => + { + i.Anchor = Anchor.Centre; + i.Origin = Anchor.Centre; + i.RelativeSizeAxes = Axes.Both; + i.Colour = Color4Extensions.FromHex(@"f7dd55"); + i.Size = new Vector2(0.64f); + }), } }, new OsuSpriteText diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 8e85eb4eb2..8ddae67dba 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -41,25 +40,8 @@ namespace osu.Game.Screens.Select.Leaderboards } } - public APILegacyUserTopScoreInfo TopScore - { - get => topScoreContainer.Score.Value; - set - { - if (value == null) - topScoreContainer.Hide(); - else - { - topScoreContainer.Show(); - topScoreContainer.Score.Value = value; - } - } - } - private bool filterMods; - private UserTopScoreContainer topScoreContainer; - private IBindable> itemRemoved; /// @@ -101,11 +83,6 @@ namespace osu.Game.Screens.Select.Leaderboards UpdateScores(); }; - Content.Add(topScoreContainer = new UserTopScoreContainer - { - ScoreSelected = s => ScoreSelected?.Invoke(s) - }); - itemRemoved = scoreManager.ItemRemoved.GetBoundCopy(); itemRemoved.BindValueChanged(onScoreRemoved); } @@ -183,7 +160,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); - TopScore = r.UserScore; + TopScore = r.UserScore?.CreateScoreInfo(rulesets); }; return req; @@ -193,5 +170,10 @@ namespace osu.Game.Screens.Select.Leaderboards { Action = () => ScoreSelected?.Invoke(model) }; + + protected override LeaderboardScore CreateDrawableTopScore(ScoreInfo model) => new LeaderboardScore(model, model.Position, false) + { + Action = () => ScoreSelected?.Invoke(model) + }; } } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 96a48fa3ac..8692833a21 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -76,9 +76,7 @@ namespace osu.Game.Screens.Select item.Ruleset.Value = Ruleset.Value; item.RequiredMods.Clear(); - item.RequiredMods.AddRange(Mods.Value); - - Mods.Value = Mods.Value.Select(m => m.CreateCopy()).ToArray(); + item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 74a5ee8309..ddbb021054 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -32,6 +31,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Graphics.UserInterface; @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); - [Resolved(canBeNull: true)] + [Resolved] private MusicController music { get; set; } [BackgroundDependencyLoader(true)] @@ -561,15 +561,15 @@ namespace osu.Game.Screens.Select BeatmapDetails.Refresh(); - Beatmap.Value.Track.Looping = true; - music?.ResetTrackAdjustments(); + music.CurrentTrack.Looping = true; + music.ResetTrackAdjustments(); if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { updateComponentFromBeatmap(Beatmap.Value); // restart playback on returning to song select, regardless. - music?.Play(); + music.Play(); } this.FadeIn(250); @@ -586,8 +586,7 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); - if (Beatmap.Value.Track != null) - Beatmap.Value.Track.Looping = false; + music.CurrentTrack.Looping = false; this.ScaleTo(1.1f, 250, Easing.InSine); @@ -608,8 +607,7 @@ namespace osu.Game.Screens.Select FilterControl.Deactivate(); - if (Beatmap.Value.Track != null) - Beatmap.Value.Track.Looping = false; + music.CurrentTrack.Looping = false; return false; } @@ -650,11 +648,10 @@ namespace osu.Game.Screens.Select BeatmapDetails.Beatmap = beatmap; - if (beatmap.Track != null) - beatmap.Track.Looping = true; + music.CurrentTrack.Looping = true; } - private readonly WeakReference lastTrack = new WeakReference(null); + private readonly WeakReference lastTrack = new WeakReference(null); /// /// Ensures some music is playing for the current track. @@ -662,14 +659,14 @@ namespace osu.Game.Screens.Select /// private void ensurePlayingSelected() { - Track track = Beatmap.Value.Track; + ITrack track = music.CurrentTrack; bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack)) - music?.Play(true); + if (!track.IsRunning && (music.IsUserPaused != true || isNewTrack)) + music.Play(true); lastTrack.SetTarget(track); } diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs index c3e36c8e9d..e5e134fd39 100644 --- a/osu.Game/Screens/StartupScreen.cs +++ b/osu.Game/Screens/StartupScreen.cs @@ -18,6 +18,6 @@ namespace osu.Game.Screens public override bool AllowRateAdjustments => false; - public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 6ada632850..e492069c5e 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) { diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index cdf9170701..bfcb2403c1 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -37,6 +37,6 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => null; - protected override Track GetTrack() => null; + protected override Track GetBeatmapTrack() => null; } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 866fc215d6..756074c0b3 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -20,6 +20,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -135,6 +136,9 @@ namespace osu.Game.Tests.Visual [Resolved] protected AudioManager Audio { get; private set; } + [Resolved] + protected MusicController MusicController { get; private set; } + /// /// Creates the ruleset to be used for this test scene. /// @@ -164,8 +168,8 @@ namespace osu.Game.Tests.Visual rulesetDependencies?.Dispose(); - if (Beatmap?.Value.TrackLoaded == true) - Beatmap.Value.Track.Stop(); + if (MusicController?.TrackLoaded == true) + MusicController.CurrentTrack.Stop(); if (contextFactory.IsValueCreated) contextFactory.Value.ResetDatabase(); @@ -219,7 +223,7 @@ namespace osu.Game.Tests.Visual store?.Dispose(); } - protected override Track GetTrack() => track; + protected override Track GetBeatmapTrack() => track; public class TrackVirtualStore : AudioCollectionManager, ITrackStore { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d1e2033596..526dca421a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,9 +24,9 @@ - - - + + + diff --git a/osu.iOS.props b/osu.iOS.props index 9b25eaab41..0cbbba70b9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,8 +70,8 @@ - - + + @@ -80,7 +80,7 @@ - +