From d83abfa7d2cbcfcdb2922f1756a3b2b8fac18ff5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jan 2021 23:29:32 +0300 Subject: [PATCH 001/304] Add test scene with failing test case --- .../TestSceneUpdateableBeatmapSetCover.cs | 130 ++++++++++++++++++ .../Drawables/UpdateableBeatmapSetCover.cs | 14 +- 2 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs new file mode 100644 index 0000000000..7daaae45a4 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -0,0 +1,130 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUpdateableBeatmapSetCover : OsuTestScene + { + [Test] + public void TestLocal([Values] BeatmapSetCoverType coverType) + { + AddStep("setup cover", () => Child = new UpdateableBeatmapSetCover + { + CoverType = coverType, + BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, + RelativeSizeAxes = Axes.Both, + Masking = true, + }); + + AddUntilStep("wait for load", () => this.ChildrenOfType().SingleOrDefault()?.IsLoaded ?? false); + } + + [Test] + public void TestUnloadAndReload() + { + OsuScrollContainer scroll = null; + List covers = new List(); + + AddStep("setup covers", () => + { + BeatmapSetInfo setInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + + FillFlowContainer fillFlow; + + Child = scroll = new OsuScrollContainer + { + Size = new Vector2(500f), + Child = fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding { Bottom = 550 } + } + }; + + var coverTypes = Enum.GetValues(); + + for (int i = 0; i < 25; i++) + { + var coverType = coverTypes[i % coverTypes.Length]; + + var cover = new UpdateableBeatmapSetCover + { + CoverType = coverType, + BeatmapSet = setInfo, + Height = 100, + Masking = true, + }; + + if (coverType == BeatmapSetCoverType.Cover) + cover.Width = 500; + else if (coverType == BeatmapSetCoverType.Card) + cover.Width = 400; + else if (coverType == BeatmapSetCoverType.List) + cover.Size = new Vector2(100, 50); + + fillFlow.Add(cover); + covers.Add(cover); + } + }); + + var loadedCovers = covers.Where(c => c.ChildrenOfType().SingleOrDefault()?.IsLoaded ?? false); + + AddUntilStep("some loaded", () => loadedCovers.Any()); + AddStep("scroll to end", () => scroll.ScrollToEnd()); + AddUntilStep("all unloaded", () => !loadedCovers.Any()); + } + + [Test] + public void TestSetNullBeatmapWhileLoading() + { + TestUpdateableBeatmapSetCover updateableCover = null; + + AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover + { + BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, + RelativeSizeAxes = Axes.Both, + Masking = true, + }); + + AddStep("change model", () => updateableCover.BeatmapSet = null); + AddWaitStep("wait some", 5); + AddAssert("no cover added", () => !updateableCover.ChildrenOfType().Any()); + } + + private class TestUpdateableBeatmapSetCover : UpdateableBeatmapSetCover + { + protected override BeatmapSetCover CreateBeatmapSetCover(BeatmapSetInfo beatmapSet, BeatmapSetCoverType coverType) => new TestBeatmapSetCover(beatmapSet, coverType); + } + + private class TestBeatmapSetCover : BeatmapSetCover + { + public TestBeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) + : base(set, type) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Thread.Sleep(10000); + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 6c229755e7..4b0430381b 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -59,6 +59,8 @@ namespace osu.Game.Beatmaps.Drawables updateCover(); } + protected virtual BeatmapSetCover CreateBeatmapSetCover(BeatmapSetInfo beatmapSet, BeatmapSetCoverType coverType) => new BeatmapSetCover(beatmapSet, coverType); + private void updateCover() { displayedCover?.FadeOut(400); @@ -69,13 +71,11 @@ namespace osu.Game.Beatmaps.Drawables { Add(displayedCover = new DelayedLoadUnloadWrapper(() => { - var cover = new BeatmapSetCover(beatmapSet, coverType) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - }; + var cover = CreateBeatmapSetCover(beatmapSet, coverType); + cover.Anchor = Anchor.Centre; + cover.Origin = Anchor.Centre; + cover.RelativeSizeAxes = Axes.Both; + cover.FillMode = FillMode.Fill; cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); return cover; })); From acfb2d2980a99d7f88657db43712a3ed88caaac6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jan 2021 23:28:46 +0300 Subject: [PATCH 002/304] Refactor beatmap set covers into using `ModelBackedDrawable` --- .../TestSceneUpdateableBeatmapSetCover.cs | 24 ++++-- .../Drawables/UpdateableBeatmapSetCover.cs | 79 ++++++------------- .../Historical/DrawableMostPlayedBeatmap.cs | 3 +- 3 files changed, 44 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index 7daaae45a4..2ffa7e2668 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -22,9 +22,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestLocal([Values] BeatmapSetCoverType coverType) { - AddStep("setup cover", () => Child = new UpdateableBeatmapSetCover + AddStep("setup cover", () => Child = new UpdateableBeatmapSetCover(coverType) { - CoverType = coverType, BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, RelativeSizeAxes = Axes.Both, Masking = true, @@ -64,9 +63,8 @@ namespace osu.Game.Tests.Visual.UserInterface { var coverType = coverTypes[i % coverTypes.Length]; - var cover = new UpdateableBeatmapSetCover + var cover = new UpdateableBeatmapSetCover(coverType) { - CoverType = coverType, BeatmapSet = setInfo, Height = 100, Masking = true, @@ -110,13 +108,25 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestUpdateableBeatmapSetCover : UpdateableBeatmapSetCover { - protected override BeatmapSetCover CreateBeatmapSetCover(BeatmapSetInfo beatmapSet, BeatmapSetCoverType coverType) => new TestBeatmapSetCover(beatmapSet, coverType); + protected override Drawable CreateDrawable(BeatmapSetInfo model) + { + if (model == null) + return null; + + return new TestBeatmapSetCover(model) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + }; + } } private class TestBeatmapSetCover : BeatmapSetCover { - public TestBeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) - : base(set, type) + public TestBeatmapSetCover(BeatmapSetInfo set) + : base(set) { } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 4b0430381b..3a4423e42e 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -8,78 +9,50 @@ using osu.Game.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class UpdateableBeatmapSetCover : Container + public class UpdateableBeatmapSetCover : ModelBackedDrawable { - private Drawable displayedCover; - - private BeatmapSetInfo beatmapSet; + private readonly BeatmapSetCoverType coverType; public BeatmapSetInfo BeatmapSet { - get => beatmapSet; - set - { - if (value == beatmapSet) return; - - beatmapSet = value; - - if (IsLoaded) - updateCover(); - } + get => Model; + set => Model = value; } - private BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover; - - public BeatmapSetCoverType CoverType + public new bool Masking { - get => coverType; - set - { - if (value == coverType) return; - - coverType = value; - - if (IsLoaded) - updateCover(); - } + get => base.Masking; + set => base.Masking = value; } - public UpdateableBeatmapSetCover() + public UpdateableBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover) { - Child = new Box + this.coverType = coverType; + + InternalChild = new Box { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f), }; } - protected override void LoadComplete() + protected override double TransformDuration => 400.0; + + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad); + + protected override Drawable CreateDrawable(BeatmapSetInfo model) { - base.LoadComplete(); - updateCover(); - } + if (model == null) + return null; - protected virtual BeatmapSetCover CreateBeatmapSetCover(BeatmapSetInfo beatmapSet, BeatmapSetCoverType coverType) => new BeatmapSetCover(beatmapSet, coverType); - - private void updateCover() - { - displayedCover?.FadeOut(400); - displayedCover?.Expire(); - displayedCover = null; - - if (beatmapSet != null) + return new BeatmapSetCover(model, coverType) { - Add(displayedCover = new DelayedLoadUnloadWrapper(() => - { - var cover = CreateBeatmapSetCover(beatmapSet, coverType); - cover.Anchor = Anchor.Centre; - cover.Origin = Anchor.Centre; - cover.RelativeSizeAxes = Axes.Both; - cover.FillMode = FillMode.Fill; - cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); - return cover; - })); - } + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }; } } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 5b7c5efbe2..f409411953 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -41,12 +41,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { AddRangeInternal(new Drawable[] { - new UpdateableBeatmapSetCover + new UpdateableBeatmapSetCover(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Y, Width = cover_width, BeatmapSet = beatmap.BeatmapSet, - CoverType = BeatmapSetCoverType.List, }, new Container { From faa221027444b36fd3089888119c39964bbdbe4a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jan 2021 00:55:45 +0300 Subject: [PATCH 003/304] Use old-style Enum.GetValues method for older than .NET 5 --- .../UserInterface/TestSceneUpdateableBeatmapSetCover.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index 2ffa7e2668..2f60fbcda8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -57,11 +57,15 @@ namespace osu.Game.Tests.Visual.UserInterface } }; +#if NET5_0 var coverTypes = Enum.GetValues(); +#else + var coverTypes = Enum.GetValues(typeof(BeatmapSetCoverType)).Cast(); +#endif for (int i = 0; i < 25; i++) { - var coverType = coverTypes[i % coverTypes.Length]; + var coverType = coverTypes.ElementAt(i); var cover = new UpdateableBeatmapSetCover(coverType) { From 85b8b00b8c10ff23ca10169d5ca9940ea67eb1bb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jan 2021 00:59:16 +0300 Subject: [PATCH 004/304] Fix forgotten modulo --- .../Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index 2f60fbcda8..0b9bcad9fe 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.UserInterface for (int i = 0; i < 25; i++) { - var coverType = coverTypes.ElementAt(i); + var coverType = coverTypes.ElementAt(i % coverTypes.Count()); var cover = new UpdateableBeatmapSetCover(coverType) { From d034728443f395cccc3b0764158ed679a738fb93 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jan 2021 01:05:45 +0300 Subject: [PATCH 005/304] Remove conditional compilation symbols for such case --- .../UserInterface/TestSceneUpdateableBeatmapSetCover.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index 0b9bcad9fe..ecb076d356 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -57,11 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface } }; -#if NET5_0 - var coverTypes = Enum.GetValues(); -#else var coverTypes = Enum.GetValues(typeof(BeatmapSetCoverType)).Cast(); -#endif for (int i = 0; i < 25; i++) { From 512cec3458f53c29b135d65d49a4af8b86be8fe2 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 00:08:16 +0800 Subject: [PATCH 006/304] Use unicode for playlists --- .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 23c713a2c1..4fccf45fda 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -13,11 +13,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; @@ -105,10 +107,18 @@ namespace osu.Game.Screens.OnlinePlay private void refresh() { + var beatmapMetadata = Item.Beatmap.Value.Metadata ?? Item.Beatmap.Value.BeatmapSet?.Metadata; difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + var text = new List + { + new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.ArtistUnicode, beatmapMetadata.Artist) }, + new OsuSpriteText { Text = " - " }, + new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.TitleUnicode, beatmapMetadata.Title) }, + new OsuSpriteText { Text = $" ({beatmapMetadata.Author}) [{Item.Beatmap.Value.Version}]" } + }; + beatmapText.AddLink(text, LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From cfaaf2e83e3112ea16e786c9a7487a58e794cd17 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 09:52:25 +0800 Subject: [PATCH 007/304] Add ToRomanisableString() --- osu.Game/Beatmaps/BeatmapInfo.cs | 12 ++++++++---- osu.Game/Beatmaps/BeatmapMetadata.cs | 7 +++++++ .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 11 ++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index bf7906bd5c..c5c5bd208c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.IO.Serialization; @@ -127,6 +128,8 @@ namespace osu.Game.Beatmaps // Metadata public string Version { get; set; } + public string VersionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } @@ -143,11 +146,12 @@ namespace osu.Game.Beatmaps Version }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); - public override string ToString() - { - string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {VersionString}".Trim(); - return $"{Metadata ?? BeatmapSet?.Metadata} {version}".Trim(); + public RomanisableString ToRomanisableString() + { + var metadata = (Metadata ?? BeatmapSet?.Metadata).ToRomanisableString() ?? new RomanisableString(null, null); + return new RomanisableString($"{metadata.GetPreferred(true)} {VersionString}".Trim(), $"{metadata.GetPreferred(false)} {VersionString}".Trim()); } public bool Equals(BeatmapInfo other) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 858da8e602..02d1349794 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Users; @@ -71,6 +72,12 @@ namespace osu.Game.Beatmaps return $"{Artist} - {Title} {author}".Trim(); } + public RomanisableString ToRomanisableString() + { + string author = Author == null ? string.Empty : $"({Author})"; + return new RomanisableString($"{ArtistUnicode} - {TitleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); + } + [JsonIgnore] public string[] SearchableTerms => new[] { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 4fccf45fda..69173bd53b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -107,18 +107,11 @@ namespace osu.Game.Screens.OnlinePlay private void refresh() { - var beatmapMetadata = Item.Beatmap.Value.Metadata ?? Item.Beatmap.Value.BeatmapSet?.Metadata; difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - var text = new List - { - new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.ArtistUnicode, beatmapMetadata.Artist) }, - new OsuSpriteText { Text = " - " }, - new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.TitleUnicode, beatmapMetadata.Title) }, - new OsuSpriteText { Text = $" ({beatmapMetadata.Author}) [{Item.Beatmap.Value.Version}]" } - }; - beatmapText.AddLink(text, LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + beatmapText.AddLink(new List{ new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } } + , LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From 1339c126a49d9d52def6ef031959812f3ea48794 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 09:54:50 +0800 Subject: [PATCH 008/304] Remove unused using --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 69173bd53b..ca8e13d10a 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; From 646403b826da7d5b3e2600ea93ac2ab2c01e9346 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 10:00:17 +0800 Subject: [PATCH 009/304] Fix CI errors --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index c5c5bd208c..d5a1e2491b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -150,7 +150,7 @@ namespace osu.Game.Beatmaps public RomanisableString ToRomanisableString() { - var metadata = (Metadata ?? BeatmapSet?.Metadata).ToRomanisableString() ?? new RomanisableString(null, null); + var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null); return new RomanisableString($"{metadata.GetPreferred(true)} {VersionString}".Trim(), $"{metadata.GetPreferred(false)} {VersionString}".Trim()); } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index ca8e13d10a..fe5876e732 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -109,8 +109,8 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(new List{ new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } } - , LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + beatmapText.AddLink(new List { new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } }, + LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From 38a7c590c4d07ac17c9746c8feac2aa4a59c63af Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 20:57:25 +0800 Subject: [PATCH 010/304] Make versionString private --- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index d5a1e2491b..36cb97e8d7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps // Metadata public string Version { get; set; } - public string VersionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } @@ -146,12 +146,12 @@ namespace osu.Game.Beatmaps Version }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); - public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {VersionString}".Trim(); + public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {versionString}".Trim(); public RomanisableString ToRomanisableString() { var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null); - return new RomanisableString($"{metadata.GetPreferred(true)} {VersionString}".Trim(), $"{metadata.GetPreferred(false)} {VersionString}".Trim()); + return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); } public bool Equals(BeatmapInfo other) From 488001d5701b51db7405e5ebd7c3ab4174e9a699 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 20:58:08 +0800 Subject: [PATCH 011/304] Support SpriteText for LinkFlowContainer --- .../Graphics/Containers/LinkFlowContainer.cs | 23 +++++++++++++++++++ osu.Game/Online/Chat/DrawableLinkCompiler.cs | 5 ++++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 914c8ff78d..32efcce80c 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -67,6 +67,13 @@ namespace osu.Game.Graphics.Containers createLink(text, new LinkDetails(action, linkArgument), tooltipText); } + public void AddLink(SpriteText text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) + { + AddArbitraryDrawable(text); + + createLink(text, new LinkDetails(action, linkArgument), tooltipText); + } + public void AddUserLink(User user, Action creationParameters = null) => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); @@ -86,6 +93,22 @@ namespace osu.Game.Graphics.Containers }); } + private void createLink(Drawable drawable, LinkDetails link, string tooltipText, Action action = null) + { + AddInternal(new DrawableLinkCompiler(drawable) + { + RelativeSizeAxes = Axes.Both, + TooltipText = tooltipText, + Action = () => + { + if (action != null) + action(); + else + game?.HandleLink(link); + }, + }); + } + // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index d27a3fbffe..7675eab4c0 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -31,6 +31,11 @@ namespace osu.Game.Online.Chat Parts = parts.ToList(); } + public DrawableLinkCompiler(Drawable part) + { + Parts = new List { part }; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index fe5876e732..a92ca610f3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(new List { new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } }, + beatmapText.AddLink(new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() }, LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From ef3801b5dd619d1fb0c5edf32c4d683ad26bd4ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 15:29:26 +0900 Subject: [PATCH 012/304] Add helper method supporting RomanisableString --- .../Graphics/Containers/LinkFlowContainer.cs | 34 ++++++------------- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 4 +-- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 32efcce80c..3a2f9d5a78 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -7,7 +7,10 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; using osu.Game.Users; namespace osu.Game.Graphics.Containers @@ -59,6 +62,14 @@ namespace osu.Game.Graphics.Containers public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); + public void AddLink(RomanisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + { + var spriteText = new OsuSpriteText { Text = text }; + + AddText(spriteText, creationParameters); + createLink(spriteText.Yield(), new LinkDetails(action, argument), tooltipText); + } + public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) { foreach (var t in text) @@ -67,13 +78,6 @@ namespace osu.Game.Graphics.Containers createLink(text, new LinkDetails(action, linkArgument), tooltipText); } - public void AddLink(SpriteText text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) - { - AddArbitraryDrawable(text); - - createLink(text, new LinkDetails(action, linkArgument), tooltipText); - } - public void AddUserLink(User user, Action creationParameters = null) => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); @@ -93,22 +97,6 @@ namespace osu.Game.Graphics.Containers }); } - private void createLink(Drawable drawable, LinkDetails link, string tooltipText, Action action = null) - { - AddInternal(new DrawableLinkCompiler(drawable) - { - RelativeSizeAxes = Axes.Both, - TooltipText = tooltipText, - Action = () => - { - if (action != null) - action(); - else - game?.HandleLink(link); - }, - }); - } - // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index a92ca610f3..38a9ace619 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -18,7 +18,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; @@ -109,8 +108,7 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() }, - LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + beatmapText.AddLink(Item.Beatmap.Value.ToRomanisableString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From 25c7dc9ef0f7716a08878733b9cba18063cfef48 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 15:30:11 +0900 Subject: [PATCH 013/304] Revert unnecessary change --- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 7675eab4c0..d27a3fbffe 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -31,11 +31,6 @@ namespace osu.Game.Online.Chat Parts = parts.ToList(); } - public DrawableLinkCompiler(Drawable part) - { - Parts = new List { part }; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { From 2a7ef1f80f037cbf393acbf23f1bd06ee4cbc4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Apr 2021 19:27:35 +0200 Subject: [PATCH 014/304] Use more general type --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 3a2f9d5a78..054febeec3 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -62,7 +62,7 @@ namespace osu.Game.Graphics.Containers public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); - public void AddLink(RomanisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) { var spriteText = new OsuSpriteText { Text = text }; From 623eae15762f4cf796dfbd2fbab26b64a3aaa142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 17:06:01 +0900 Subject: [PATCH 015/304] Add basic language switching ability --- .../ResourceManagerLocalisationStore.cs | 57 +++++++++++++++++++ osu.Game/OsuGame.cs | 7 +++ .../Sections/General/LanguageSettings.cs | 18 ++++++ 3 files changed, 82 insertions(+) create mode 100644 osu.Game/Localisation/ResourceManagerLocalisationStore.cs diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs new file mode 100644 index 0000000000..dd84eff55f --- /dev/null +++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Resources; +using System.Threading.Tasks; +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public class ResourceManagerLocalisationStore : ILocalisationStore + { + private readonly Dictionary resourceManagers = new Dictionary(); + + public ResourceManagerLocalisationStore(string cultureCode) + { + EffectiveCulture = new CultureInfo(cultureCode); + } + + public void Dispose() + { + } + + public string Get(string lookup) + { + var split = lookup.Split(':'); + + string ns = split[0]; + string key = split[1]; + + if (!resourceManagers.TryGetValue(ns, out var manager)) + resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly); + + return manager.GetString(key, EffectiveCulture); + } + + public Task GetAsync(string lookup) + { + return Task.FromResult(Get(lookup)); + } + + public Stream GetStream(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAvailableResources() + { + throw new NotImplementedException(); + } + + public CultureInfo EffectiveCulture { get; } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 28f32ba455..9af9a34b46 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -51,6 +51,7 @@ using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; using osu.Game.Database; using osu.Game.IO; +using osu.Game.Localisation; namespace osu.Game { @@ -541,6 +542,12 @@ namespace osu.Game { base.LoadComplete(); + foreach (var language in Enum.GetValues(typeof(Language)).OfType()) + { + var cultureCode = language.ToString(); + Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode)); + } + // The next time this is updated is in UpdateAfterChildren, which occurs too late and results // in the cursor being shown for a few frames during the intro. // This prevents the cursor from showing until we have a screen with CursorVisible = true diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index 44e42ecbfe..c2767f61b4 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -1,27 +1,45 @@ // 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.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.General { public class LanguageSettings : SettingsSubsection { + private SettingsDropdown languageSelection; + private Bindable frameworkLocale; + protected override string Header => "Language"; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { + frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); + Children = new Drawable[] { + languageSelection = new SettingsEnumDropdown + { + LabelText = "Language", + }, new SettingsCheckbox { LabelText = "Prefer metadata in original language", Current = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode) }, }; + + if (!Enum.TryParse(frameworkLocale.Value, out var locale)) + locale = Language.en; + languageSelection.Current.Value = locale; + + languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToString()); } } } From db524e2395b375e6882eaef9b5424380f70eaa23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 17:06:12 +0900 Subject: [PATCH 016/304] Add localisation support to DialogButton's text --- osu.Game/Graphics/UserInterface/DialogButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 9b53ee7b2d..1047aa4255 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -180,9 +181,9 @@ namespace osu.Game.Graphics.UserInterface } } - private string text; + private LocalisableString text; - public string Text + public LocalisableString Text { get => text; set From 60acd824cbc0c1ce6d7d3ff95bd9759e943f2dd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 17:06:26 +0900 Subject: [PATCH 017/304] Add two sample implementations --- osu.Game/Localisation/Common.ja.resx | 17 ++++++++++++++++ osu.Game/Localisation/Common.resx | 17 ++++++++++++++++ osu.Game/Localisation/CommonStrings.cs | 19 ++++++++++++++++++ osu.Game/Localisation/Language.cs | 16 +++++++++++++++ osu.Game/Localisation/MainMenu.ja.resx | 17 ++++++++++++++++ osu.Game/Localisation/MainMenu.resx | 17 ++++++++++++++++ osu.Game/Localisation/MainMenuStrings.cs | 24 +++++++++++++++++++++++ osu.Game/Overlays/Dialog/ConfirmDialog.cs | 2 +- osu.Game/Screens/Menu/Button.cs | 3 ++- osu.Game/Screens/Menu/ButtonSystem.cs | 8 +++++--- 10 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Localisation/Common.ja.resx create mode 100644 osu.Game/Localisation/Common.resx create mode 100644 osu.Game/Localisation/CommonStrings.cs create mode 100644 osu.Game/Localisation/Language.cs create mode 100644 osu.Game/Localisation/MainMenu.ja.resx create mode 100644 osu.Game/Localisation/MainMenu.resx create mode 100644 osu.Game/Localisation/MainMenuStrings.cs diff --git a/osu.Game/Localisation/Common.ja.resx b/osu.Game/Localisation/Common.ja.resx new file mode 100644 index 0000000000..174751c455 --- /dev/null +++ b/osu.Game/Localisation/Common.ja.resx @@ -0,0 +1,17 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + やめとくわ + + diff --git a/osu.Game/Localisation/Common.resx b/osu.Game/Localisation/Common.resx new file mode 100644 index 0000000000..f63fb90086 --- /dev/null +++ b/osu.Game/Localisation/Common.resx @@ -0,0 +1,17 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs new file mode 100644 index 0000000000..f448158191 --- /dev/null +++ b/osu.Game/Localisation/CommonStrings.cs @@ -0,0 +1,19 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class CommonStrings + { + private const string prefix = "osu.Game.Localisation.Common"; + + /// + /// "Cancel" + /// + public static LocalisableString Cancel => new TranslatableString(getKey("cancel"), "Cancel"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs new file mode 100644 index 0000000000..edcf264c7f --- /dev/null +++ b/osu.Game/Localisation/Language.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Localisation +{ + public enum Language + { + [Description("English")] + en, + + [Description("日本語")] + ja + } +} diff --git a/osu.Game/Localisation/MainMenu.ja.resx b/osu.Game/Localisation/MainMenu.ja.resx new file mode 100644 index 0000000000..20c85110ad --- /dev/null +++ b/osu.Game/Localisation/MainMenu.ja.resx @@ -0,0 +1,17 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ソロ + + \ No newline at end of file diff --git a/osu.Game/Localisation/MainMenu.resx b/osu.Game/Localisation/MainMenu.resx new file mode 100644 index 0000000000..845b412d88 --- /dev/null +++ b/osu.Game/Localisation/MainMenu.resx @@ -0,0 +1,17 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + solo + + diff --git a/osu.Game/Localisation/MainMenuStrings.cs b/osu.Game/Localisation/MainMenuStrings.cs new file mode 100644 index 0000000000..fd9647467a --- /dev/null +++ b/osu.Game/Localisation/MainMenuStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class MainMenuStrings + { + private const string prefix = "osu.Game.Localisation.MainMenu"; + + /// + /// "solo" + /// + public static LocalisableString Solo => new TranslatableString(getKey("solo"), "solo"); + + /// + /// "multi" + /// + public static LocalisableString Multi => new TranslatableString(getKey("multi"), "multi"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Overlays/Dialog/ConfirmDialog.cs b/osu.Game/Overlays/Dialog/ConfirmDialog.cs index a87c06ffdf..d1c0d746d1 100644 --- a/osu.Game/Overlays/Dialog/ConfirmDialog.cs +++ b/osu.Game/Overlays/Dialog/ConfirmDialog.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Dialog }, new PopupDialogCancelButton { - Text = @"Cancel", + Text = Localisation.CommonStrings.Cancel, Action = onCancel }, }; diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index d956394ebb..26f26d1304 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Screens.Menu @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Menu public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); - public Button(string text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) + public Button(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) { this.sampleName = sampleName; this.clickAction = clickAction; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 81b1cb0bf1..ff5ad37b9d 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; @@ -22,6 +23,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -121,10 +123,10 @@ namespace osu.Game.Screens.Menu private LoginOverlay loginOverlay { get; set; } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) + private void load(AudioManager audio, IdleTracker idleTracker, GameHost host, LocalisationManager strings) { - buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); + buttonsPlay.Add(new Button(MainMenuStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new Button(MainMenuStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); buttonsPlay.Add(new Button(@"playlists", @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); From 5c8f5624724095c7c98961cb9a857be778aee3c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 14:37:28 +0900 Subject: [PATCH 018/304] Don't bail if the underlying localisation resourced is not embedded --- .../Localisation/ResourceManagerLocalisationStore.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs index dd84eff55f..e0f110aba9 100644 --- a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs +++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs @@ -34,7 +34,16 @@ namespace osu.Game.Localisation if (!resourceManagers.TryGetValue(ns, out var manager)) resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly); - return manager.GetString(key, EffectiveCulture); + try + { + return manager.GetString(key, EffectiveCulture); + } + catch (MissingManifestResourceException) + { + // in the case the manifest is missing, it is likely that the user is adding code-first implementations of new localisation namespaces. + // it's fine to ignore this as localisation will fallback to default values. + return null; + } } public Task GetAsync(string lookup) From 31c8586dacb5b5234c61d8e83f5546937df903cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 14:37:11 +0900 Subject: [PATCH 019/304] Add localisation support to overlay header title/description --- osu.Game/Localisation/ChatStrings.cs | 24 +++++++++++++++++++ osu.Game/Localisation/NotificationsStrings.cs | 24 +++++++++++++++++++ osu.Game/Localisation/NowPlayingStrings.cs | 24 +++++++++++++++++++ osu.Game/Localisation/SettingsStrings.cs | 24 +++++++++++++++++++ osu.Game/Overlays/ChatOverlay.cs | 6 +++-- osu.Game/Overlays/FullscreenOverlay.cs | 5 ++-- osu.Game/Overlays/INamedOverlayComponent.cs | 6 +++-- osu.Game/Overlays/NotificationOverlay.cs | 6 +++-- osu.Game/Overlays/NowPlayingOverlay.cs | 5 ++-- osu.Game/Overlays/OverlayTitle.cs | 7 +++--- osu.Game/Overlays/Settings/SettingsHeader.cs | 7 +++--- osu.Game/Overlays/SettingsOverlay.cs | 6 +++-- 12 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Localisation/ChatStrings.cs create mode 100644 osu.Game/Localisation/NotificationsStrings.cs create mode 100644 osu.Game/Localisation/NowPlayingStrings.cs create mode 100644 osu.Game/Localisation/SettingsStrings.cs diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs new file mode 100644 index 0000000000..daddb602ad --- /dev/null +++ b/osu.Game/Localisation/ChatStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class ChatStrings + { + private const string prefix = "osu.Game.Localisation.Chat"; + + /// + /// "chat" + /// + public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "chat"); + + /// + /// "join the real-time discussion" + /// + public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "join the real-time discussion"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs new file mode 100644 index 0000000000..092eec3a6b --- /dev/null +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class NotificationsStrings + { + private const string prefix = "osu.Game.Localisation.Notifications"; + + /// + /// "notifications" + /// + public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "notifications"); + + /// + /// "waiting for 'ya" + /// + public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "waiting for 'ya"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs new file mode 100644 index 0000000000..d742a56895 --- /dev/null +++ b/osu.Game/Localisation/NowPlayingStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class NowPlayingStrings + { + private const string prefix = "osu.Game.Localisation.NowPlaying"; + + /// + /// "now playing" + /// + public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "now playing"); + + /// + /// "manage the currently playing track" + /// + public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "manage the currently playing track"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs new file mode 100644 index 0000000000..cfbd392691 --- /dev/null +++ b/osu.Game/Localisation/SettingsStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class SettingsStrings + { + private const string prefix = "osu.Game.Localisation.Settings"; + + /// + /// "settings" + /// + public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "settings"); + + /// + /// "change the way osu! behaves" + /// + public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "change the way osu! behaves"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 28f2287514..285041800a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -24,6 +24,8 @@ using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; +using osu.Game.Localisation; using osu.Game.Online; namespace osu.Game.Overlays @@ -31,8 +33,8 @@ namespace osu.Game.Overlays public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { public string IconTexture => "Icons/Hexacons/messaging"; - public string Title => "chat"; - public string Description => "join the real-time discussion"; + public LocalisableString Title => ChatStrings.HeaderTitle; + public LocalisableString Description => ChatStrings.HeaderDescription; private const float textbox_height = 60; private const float channel_selection_min_height = 0.3f; diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 735f0bcbd4..58c41c4a4b 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osuTK.Graphics; @@ -18,8 +19,8 @@ namespace osu.Game.Overlays 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 virtual LocalisableString Title => Header.Title.Title; + public virtual LocalisableString Description => Header.Title.Description; public T Header { get; } diff --git a/osu.Game/Overlays/INamedOverlayComponent.cs b/osu.Game/Overlays/INamedOverlayComponent.cs index 38fb8679a0..ca0aea041e 100644 --- a/osu.Game/Overlays/INamedOverlayComponent.cs +++ b/osu.Game/Overlays/INamedOverlayComponent.cs @@ -1,14 +1,16 @@ // 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.Localisation; + namespace osu.Game.Overlays { public interface INamedOverlayComponent { string IconTexture { get; } - string Title { get; } + LocalisableString Title { get; } - string Description { get; } + LocalisableString Description { get; } } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index d51d964fc4..b26e17b34c 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -11,16 +11,18 @@ using osu.Game.Graphics.Containers; using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Localisation; namespace osu.Game.Overlays { public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { public string IconTexture => "Icons/Hexacons/notification"; - public string Title => "notifications"; - public string Description => "waiting for 'ya"; + public LocalisableString Title => NotificationsStrings.HeaderTitle; + public LocalisableString Description => NotificationsStrings.HeaderDescription; private const float width = 320; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 81bf71cdec..f88be91c01 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Overlays.Music; using osuTK; using osuTK.Graphics; @@ -28,8 +29,8 @@ namespace osu.Game.Overlays public class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { public string IconTexture => "Icons/Hexacons/music"; - public string Title => "now playing"; - public string Description => "manage the currently playing track"; + public LocalisableString Title => NowPlayingStrings.HeaderTitle; + public LocalisableString Description => NowPlayingStrings.HeaderDescription; private const float player_height = 130; private const float transition_length = 800; diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs index c3ea35adfc..d92979e8d4 100644 --- a/osu.Game/Overlays/OverlayTitle.cs +++ b/osu.Game/Overlays/OverlayTitle.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -19,15 +20,15 @@ namespace osu.Game.Overlays private readonly OsuSpriteText titleText; private readonly Container icon; - private string title; + private LocalisableString title; - public string Title + public LocalisableString Title { get => title; protected set => titleText.Text = title = value; } - public string Description { get; protected set; } + public LocalisableString Description { get; protected set; } private string iconTexture; diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index d8ec00bd99..a7f1cef74c 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -11,10 +12,10 @@ namespace osu.Game.Overlays.Settings { public class SettingsHeader : Container { - private readonly string heading; - private readonly string subheading; + private readonly LocalisableString heading; + private readonly LocalisableString subheading; - public SettingsHeader(string heading, string subheading) + public SettingsHeader(LocalisableString heading, LocalisableString subheading) { this.heading = heading; this.subheading = subheading; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 7bd84dbc6c..8c21880cc6 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -10,14 +10,16 @@ using osuTK.Graphics; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Game.Localisation; namespace osu.Game.Overlays { public class SettingsOverlay : SettingsPanel, INamedOverlayComponent { public string IconTexture => "Icons/Hexacons/settings"; - public string Title => "settings"; - public string Description => "change the way osu! behaves"; + public LocalisableString Title => SettingsStrings.HeaderTitle; + public LocalisableString Description => SettingsStrings.HeaderDescription; protected override IEnumerable CreateSections() => new SettingsSection[] { From e536f1ad6d3bd1d00968eeddba4997ebda85218c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 14:39:36 +0900 Subject: [PATCH 020/304] Add simple locking of resourceManagers dictionary for thread safety --- .../ResourceManagerLocalisationStore.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs index e0f110aba9..7b21e1af42 100644 --- a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs +++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs @@ -31,18 +31,21 @@ namespace osu.Game.Localisation string ns = split[0]; string key = split[1]; - if (!resourceManagers.TryGetValue(ns, out var manager)) - resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly); + lock (resourceManagers) + { + if (!resourceManagers.TryGetValue(ns, out var manager)) + resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly); - try - { - return manager.GetString(key, EffectiveCulture); - } - catch (MissingManifestResourceException) - { - // in the case the manifest is missing, it is likely that the user is adding code-first implementations of new localisation namespaces. - // it's fine to ignore this as localisation will fallback to default values. - return null; + try + { + return manager.GetString(key, EffectiveCulture); + } + catch (MissingManifestResourceException) + { + // in the case the manifest is missing, it is likely that the user is adding code-first implementations of new localisation namespaces. + // it's fine to ignore this as localisation will fallback to default values. + return null; + } } } From d2629561469e4dfd0efba561b757d4da10aac481 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 18:27:40 +0900 Subject: [PATCH 021/304] Always use LifetimeEntry to manage hit objects in HitObjectContainer Previously, non-pooled DHOs were immediately added as children of the HOC when Add is called. Also, non-pooled DHOs were always attached to the HOC as children. New behavior is that non-pooled DHOs are only added after CheckChildLifetime, and only attached to the HOC while the DHOs are alive. - LifetimeManagementContainer inheritance of HOC is removed, as it is now all DHOs are "unmanaged" (previously `AddInternal(false)`). - The signature of `Clear` is changed, and it is now always not disposing the children immediately. --- .../Edit/ManiaBeatSnapGrid.cs | 2 +- .../HitObjectApplicationTestScene.cs | 2 +- .../Pooling/PoolableDrawableWithLifetime.cs | 2 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 66 +++++++++---------- .../Scrolling/ScrollingHitObjectContainer.cs | 4 +- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index afc08dcc96..9d1f5429a1 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Edit foreach (var line in grid.Objects.OfType()) availableLines.Push(line); - grid.Clear(false); + grid.Clear(); } if (selectionTimeRange == null) diff --git a/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs index a1d000386f..ac01508081 100644 --- a/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [SetUpSteps] public void SetUp() - => AddStep("clear SHOC", () => hitObjectContainer.Clear(false)); + => AddStep("clear SHOC", () => hitObjectContainer.Clear()); protected void AddHitObject(DrawableHitObject hitObject) => AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject)); diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 93e476be76..31f1768044 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// The entry holding essential state of this . /// - protected TEntry? Entry { get; private set; } + public TEntry? Entry { get; private set; } /// /// Whether is applied to this . diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 11312a46df..3d74cdedd6 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI { - public class HitObjectContainer : LifetimeManagementContainer, IHitObjectContainer + public class HitObjectContainer : CompositeDrawable, IHitObjectContainer { public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.UI private readonly Dictionary startTimeMap = new Dictionary(); private readonly Dictionary drawableMap = new Dictionary(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly Dictionary nonPooledDrawableMap = new Dictionary(); [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } @@ -72,6 +73,7 @@ namespace osu.Game.Rulesets.UI lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; + lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; } protected override void LoadAsyncComplete() @@ -86,7 +88,13 @@ namespace osu.Game.Rulesets.UI public void Add(HitObjectLifetimeEntry entry) => lifetimeManager.AddEntry(entry); - public bool Remove(HitObjectLifetimeEntry entry) => lifetimeManager.RemoveEntry(entry); + public bool Remove(HitObjectLifetimeEntry entry) + { + if (!lifetimeManager.RemoveEntry(entry)) return false; + // It has to be done here because non-pooled entry may be removed by specifying its entry. + nonPooledDrawableMap.Remove(entry); + return true; + } private void entryBecameAlive(LifetimeEntry entry) => addDrawable((HitObjectLifetimeEntry)entry); @@ -96,7 +104,8 @@ namespace osu.Game.Rulesets.UI { Debug.Assert(!drawableMap.ContainsKey(entry)); - var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); + nonPooledDrawableMap.TryGetValue(entry, out var drawable); + drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); @@ -104,7 +113,7 @@ namespace osu.Game.Rulesets.UI drawable.OnRevertResult += onRevertResult; bindStartTime(drawable); - AddInternal(drawableMap[entry] = drawable, false); + AddInternal(drawableMap[entry] = drawable); OnAdd(drawable); HitObjectUsageBegan?.Invoke(entry.HitObject); @@ -127,50 +136,42 @@ namespace osu.Game.Rulesets.UI unbindStartTime(drawable); RemoveInternal(drawable); - HitObjectUsageFinished?.Invoke(entry.HitObject); + // The hit object is not freed when the DHO was not pooled. + if (!nonPooledDrawableMap.ContainsKey(entry)) + HitObjectUsageFinished?.Invoke(entry.HitObject); } #endregion #region Non-pooling support - public virtual void Add(DrawableHitObject hitObject) + public virtual void Add(DrawableHitObject drawable) { - bindStartTime(hitObject); + if (drawable.Entry == null) + throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated"); - hitObject.OnNewResult += onNewResult; - hitObject.OnRevertResult += onRevertResult; - - AddInternal(hitObject); - OnAdd(hitObject); + nonPooledDrawableMap.Add(drawable.Entry, drawable); + Add(drawable.Entry); } - public virtual bool Remove(DrawableHitObject hitObject) + public virtual bool Remove(DrawableHitObject drawable) { - OnRemove(hitObject); - if (!RemoveInternal(hitObject)) + if (drawable.Entry == null) return false; - hitObject.OnNewResult -= onNewResult; - hitObject.OnRevertResult -= onRevertResult; - - unbindStartTime(hitObject); - - return true; + return Remove(drawable.Entry); } public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); - protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) + private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { - if (!(e.Child is DrawableHitObject hitObject)) - return; + if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable)) + OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction)); + } - if ((e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward) - || (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward)) - { - hitObject.OnKilled(); - } + protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) + { } #endregion @@ -195,12 +196,11 @@ namespace osu.Game.Rulesets.UI { } - public virtual void Clear(bool disposeChildren = true) + public virtual void Clear() { lifetimeManager.ClearEntries(); - - ClearInternal(disposeChildren); - unbindAllStartTimes(); + nonPooledDrawableMap.Clear(); + Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && drawableMap.Count == 0, "All hit objects should have been removed"); } protected override bool CheckChildrenLife() diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 289578f3d8..a9eaf3da68 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -50,9 +50,9 @@ namespace osu.Game.Rulesets.UI.Scrolling timeRange.ValueChanged += _ => layoutCache.Invalidate(); } - public override void Clear(bool disposeChildren = true) + public override void Clear() { - base.Clear(disposeChildren); + base.Clear(); toComputeLifetime.Clear(); layoutComputed.Clear(); From f55aa016bec4acd26e5525754a50661398da52b5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 15:53:07 +0900 Subject: [PATCH 022/304] Adopt HitObjectContainer change in a test Non-pooled objects are attached as children only while alive --- osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs index f2bfccb6de..8f3d3f1276 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -19,7 +20,14 @@ namespace osu.Game.Tests.Gameplay [SetUp] public void Setup() => Schedule(() => { - Child = container = new HitObjectContainer(); + Child = container = new HitObjectContainer + { + Clock = new FramedClock(new ManualClock + { + // Make sure hit objects with `StartTime == 0` are alive + CurrentTime = -1 + }) + }; }); [Test] From 799d2a3300dce4b833a2ca7e9e176d1079aa6038 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 13:00:46 +0900 Subject: [PATCH 023/304] Replace failed mania test (pooling not accounted) with a more robust test Also fix null reference in Playfield --- .../TestSceneDrawableNote.cs | 67 +++++++++++++++++++ .../TestSceneHoldNoteInput.cs | 15 +---- osu.Game/Rulesets/UI/Playfield.cs | 7 +- 3 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs new file mode 100644 index 0000000000..4a6c59e297 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneDrawableManiaHitObject : OsuTestScene + { + private readonly ManualClock clock = new ManualClock(); + + private Column column; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = new ScrollingTestContainer(ScrollingDirection.Down) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + TimeRange = 2000, + Clock = new FramedClock(clock), + Child = column = new Column(0) + { + Action = { Value = ManiaAction.Key1 }, + Height = 0.85f, + AccentColour = Color4.Gray + }, + }; + }); + + [Test] + public void TestHoldNoteHeadVisibility() + { + DrawableHoldNote note = null; + AddStep("Add hold note", () => + { + var h = new HoldNote + { + StartTime = 0, + Duration = 1000 + }; + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + column.Add(note = new DrawableHoldNote(h)); + }); + AddStep("Hold key", () => + { + clock.CurrentTime = 0; + note.OnPressed(ManiaAction.Key1); + }); + AddStep("progress time", () => clock.CurrentTime = 500); + AddAssert("head is visible", () => note.Head.Alpha == 1); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 668487f673..387c5f4195 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -4,14 +4,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; @@ -411,17 +408,7 @@ namespace osu.Game.Rulesets.Mania.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); - AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); - - AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head); - AddAssert("head is visible", - () => currentPlayer.ChildrenOfType() - .Single(note => note.HitObject == beatmap.HitObjects[0]) - .Head - .Alpha == 1); - - AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor?.HasCompleted.Value == true); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 17d3cf01a4..b154288dba 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -354,8 +354,11 @@ namespace osu.Game.Rulesets.UI // If this is the first time this DHO is being used, then apply the DHO mods. // This is done before Apply() so that the state is updated once when the hitobject is applied. - foreach (var m in mods.OfType()) - m.ApplyToDrawableHitObjects(dho.Yield()); + if (mods != null) + { + foreach (var m in mods.OfType()) + m.ApplyToDrawableHitObjects(dho.Yield()); + } } if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) From 971ca398260275c52fb16be82bfb38993777c504 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 18 Apr 2021 21:46:30 +0900 Subject: [PATCH 024/304] Fix failing taiko tests Non-pooled DHO is now not eagerly loaded --- .../TestSceneFlyingHits.cs | 20 +++++++++---------- .../TestSceneHits.cs | 8 ++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs index 63854e7ead..5738be05d7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs @@ -20,20 +20,19 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase(HitType.Rim)] public void TestFlyingHits(HitType hitType) { - DrawableFlyingHit flyingHit = null; - AddStep("add flying hit", () => { addFlyingHit(hitType); - - // flying hits all land in one common scrolling container (and stay there for rewind purposes), - // so we need to manually get the latest one. - flyingHit = this.ChildrenOfType() - .OrderByDescending(h => h.HitObject.StartTime) - .FirstOrDefault(); }); - AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType); + AddAssert("hit type is correct", () => + { + // flying hits all land in one common scrolling container (and stay there for rewind purposes), + // so we need to manually get the latest one. + return this.ChildrenOfType() + .OrderByDescending(h => h.HitObject.StartTime) + .FirstOrDefault()?.HitObject.Type == hitType; + }); } private void addFlyingHit(HitType hitType) @@ -42,7 +41,8 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableDrumRollTick h; DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); + h.OnLoadComplete += _ => + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 87c936d386..06acdad3d6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -129,8 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableRuleset.Playfield.Add(h); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + h.OnLoadComplete += _ => + { + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), + new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + }; } private void addMissJudgement() From b88e5a31ea838406b25dfb2e57e992fbb14f89f8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 12:55:04 +0900 Subject: [PATCH 025/304] Add failing test showing lifetime not recomputed with pooled objects --- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 9931ee4a45..75a5eec6f7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -90,6 +90,20 @@ namespace osu.Game.Tests.Visual.Gameplay assertChildPosition(5); } + [TestCase("pooled")] + [TestCase("non-pooled")] + public void TestLifetimeRecomputedWhenTimeRangeChanges(string pooled) + { + var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject()); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + createTest(beatmap); + + assertDead(3); + + AddStep("increase time range", () => drawableRuleset.TimeRange.Value = 3 * time_range); + assertPosition(3, 1); + } + [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { From 5aa522b1c28cb500ac1985595234b8cd0acacb69 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 20:23:52 +0900 Subject: [PATCH 026/304] Completely delegate DHO lifetime to Entry lifetime A downside is lifetime update is not caught by LifetimeManagementContainer if used. --- .../Gameplay/TestSceneDrawableHitObject.cs | 13 ++++---- .../Pooling/PoolableDrawableWithLifetime.cs | 32 ++++++------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 2e3f192f1b..0bec02c488 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -45,15 +45,16 @@ namespace osu.Game.Tests.Gameplay AddStep("Create DHO", () => { dho = new TestDrawableHitObject(null); - dho.Apply(entry = new TestLifetimeEntry(new HitObject()) - { - LifetimeStart = 0, - LifetimeEnd = 1000, - }); + dho.Apply(entry = new TestLifetimeEntry(new HitObject())); Child = dho; }); - AddStep("KeepAlive = true", () => entry.KeepAlive = true); + AddStep("KeepAlive = true", () => + { + entry.LifetimeStart = 0; + entry.LifetimeEnd = 1000; + entry.KeepAlive = true; + }); AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue); AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500); diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 31f1768044..e94b6dca9d 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -28,14 +28,20 @@ namespace osu.Game.Rulesets.Objects.Pooling public override double LifetimeStart { - get => base.LifetimeStart; - set => setLifetime(value, LifetimeEnd); + get => Entry?.LifetimeStart ?? double.MinValue; + set + { + if (Entry != null) Entry.LifetimeStart = value; + } } public override double LifetimeEnd { - get => base.LifetimeEnd; - set => setLifetime(LifetimeStart, value); + get => Entry?.LifetimeEnd ?? double.MaxValue; + set + { + if (Entry != null) Entry.LifetimeEnd = value; + } } public override bool RemoveWhenNotAlive => false; @@ -64,11 +70,8 @@ namespace osu.Game.Rulesets.Objects.Pooling if (HasEntryApplied) free(); - setLifetime(entry.LifetimeStart, entry.LifetimeEnd); Entry = entry; - OnApply(entry); - HasEntryApplied = true; } @@ -95,27 +98,12 @@ namespace osu.Game.Rulesets.Objects.Pooling { } - private void setLifetime(double start, double end) - { - base.LifetimeStart = start; - base.LifetimeEnd = end; - - if (Entry != null) - { - Entry.LifetimeStart = start; - Entry.LifetimeEnd = end; - } - } - private void free() { Debug.Assert(Entry != null && HasEntryApplied); OnFree(Entry); - Entry = null; - setLifetime(double.MaxValue, double.MaxValue); - HasEntryApplied = false; } } From 1d023dcedbb64fcac9d4956c73de22407e51b035 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 19:53:38 +0900 Subject: [PATCH 027/304] Fix mania editor null reference --- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 384f49d9b2..8f8f45c0dd 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } + // Overriding the base because this method is called right after `Column` is changed and `DrawableObject` is not yet loaded and Parent is not set. + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; + protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { From 4cc94efb06258cc7cf5c445e6318f3ac5cf6d852 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 20:38:44 +0900 Subject: [PATCH 028/304] Fix failing mania test --- .../Editor/TestSceneNotePlacementBlueprint.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 36c34a8fb9..a162c5ec44 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Mania.Tests.Editor @@ -35,7 +34,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestPlaceBeforeCurrentTimeDownwards() { - AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10))); + AddStep("move mouse before current time", () => + { + var column = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(-100)); + }); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -45,7 +48,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestPlaceAfterCurrentTimeDownwards() { - AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("move mouse after current time", () => + { + var column = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(100)); + }); AddStep("click", () => InputManager.Click(MouseButton.Left)); From c83c8040573ad0cdd3c650487b1aa9a28d756d71 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 14:42:41 +0900 Subject: [PATCH 029/304] Expose lifetime entries from HOC --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 24 +++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 3d74cdedd6..1d32313f2b 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -19,6 +19,16 @@ namespace osu.Game.Rulesets.UI { public class HitObjectContainer : CompositeDrawable, IHitObjectContainer { + /// + /// All entries in this including dead entries. + /// + public IEnumerable Entries => allEntries; + + /// + /// All alive entries and s used by the entries. + /// + public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => drawableMap.Select(x => (x.Key, x.Value)); + public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); @@ -60,10 +70,13 @@ namespace osu.Game.Rulesets.UI internal double FutureLifetimeExtension { get; set; } private readonly Dictionary startTimeMap = new Dictionary(); + private readonly Dictionary drawableMap = new Dictionary(); - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); private readonly Dictionary nonPooledDrawableMap = new Dictionary(); + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly HashSet allEntries = new HashSet(); + [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } @@ -86,13 +99,18 @@ namespace osu.Game.Rulesets.UI #region Pooling support - public void Add(HitObjectLifetimeEntry entry) => lifetimeManager.AddEntry(entry); + public void Add(HitObjectLifetimeEntry entry) + { + allEntries.Add(entry); + lifetimeManager.AddEntry(entry); + } public bool Remove(HitObjectLifetimeEntry entry) { if (!lifetimeManager.RemoveEntry(entry)) return false; - // It has to be done here because non-pooled entry may be removed by specifying its entry. + // The entry has to be removed from the non-pooled map here because non-pooled entry may be removed by specifying its entry. nonPooledDrawableMap.Remove(entry); + allEntries.Remove(entry); return true; } From 632bb70e0f7d051c1ee82502122ce5946227cd95 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 15:04:32 +0900 Subject: [PATCH 030/304] Use entry to calculate lifetime in ScrollingHOC DHOs cannot be used to calculate lifetime, it is not created before the entry became alive. --- .../Scrolling/ScrollingHitObjectContainer.cs | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index a9eaf3da68..915bab9a51 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Layout; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -17,16 +19,18 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - /// - /// Hit objects which require lifetime computation in the next update call. - /// - private readonly HashSet toComputeLifetime = new HashSet(); - /// /// A set containing all which have an up-to-date layout. /// private readonly HashSet layoutComputed = new HashSet(); + /// + /// A conservative estimate of maximum bounding box of a + /// with respect to the start time position of the hit object. + /// It is used to calculate when the object appears inbound. + /// + protected virtual RectangleF GetDrawRectangle(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100); + [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -54,7 +58,6 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(); - toComputeLifetime.Clear(); layoutComputed.Clear(); } @@ -166,7 +169,6 @@ namespace osu.Game.Rulesets.UI.Scrolling private void onRemoveRecursive(DrawableHitObject hitObject) { - toComputeLifetime.Remove(hitObject); layoutComputed.Remove(hitObject); hitObject.DefaultsApplied -= invalidateHitObject; @@ -175,14 +177,11 @@ namespace osu.Game.Rulesets.UI.Scrolling onRemoveRecursive(nested); } - /// - /// Make this lifetime and layout computed in next update. - /// private void invalidateHitObject(DrawableHitObject hitObject) { - // Lifetime computation is delayed until next update because - // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. - toComputeLifetime.Add(hitObject); + if (hitObject.ParentHitObject == null) + updateLifetime(hitObject.Entry); + layoutComputed.Remove(hitObject); } @@ -194,13 +193,8 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - toComputeLifetime.Clear(); - - foreach (var hitObject in Objects) - { - if (hitObject.HitObject != null) - toComputeLifetime.Add(hitObject); - } + foreach (var entry in Entries) + updateLifetime(entry); layoutComputed.Clear(); @@ -220,11 +214,6 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutCache.Validate(); } - - foreach (var hitObject in toComputeLifetime) - hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); - - toComputeLifetime.Clear(); } protected override void UpdateAfterChildrenLife() @@ -247,32 +236,31 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) + private void updateLifetime(HitObjectLifetimeEntry entry) { - float originAdjustment = 0.0f; + var rectangle = GetDrawRectangle(entry); + float startOffset = 0; - // calculate the dimension of the part of the hitobject that should already be visible - // when the hitobject origin first appears inside the scrolling container switch (direction.Value) { - case ScrollingDirection.Up: - originAdjustment = hitObject.OriginPosition.Y; + case ScrollingDirection.Right: + startOffset = rectangle.Right; break; case ScrollingDirection.Down: - originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y; + startOffset = rectangle.Bottom; break; case ScrollingDirection.Left: - originAdjustment = hitObject.OriginPosition.X; + startOffset = -rectangle.Left; break; - case ScrollingDirection.Right: - originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X; + case ScrollingDirection.Up: + startOffset = -rectangle.Top; break; } - return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); + entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); } private void updateLayoutRecursive(DrawableHitObject hitObject) From 73dfb04df8ba3b45ab9fbdd5bb1934d55f61defc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 15:17:30 +0900 Subject: [PATCH 031/304] Fix uninitialized scrollLength value is used --- .../Scrolling/ScrollingHitObjectContainer.cs | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 915bab9a51..538d4d1d11 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -185,8 +185,6 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Remove(hitObject); } - private float scrollLength; - protected override void Update() { base.Update(); @@ -199,29 +197,16 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Clear(); scrollingInfo.Algorithm.Reset(); - - switch (direction.Value) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - scrollLength = DrawSize.Y; - break; - - default: - scrollLength = DrawSize.X; - break; - } - layoutCache.Validate(); } } + // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes + // to prevent hit objects displayed in a wrong position for one frame. protected override void UpdateAfterChildrenLife() { base.UpdateAfterChildrenLife(); - // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes - // to prevent hit objects displayed in a wrong position for one frame. // Only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes). foreach (var obj in AliveObjects) { @@ -260,7 +245,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); + entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, getLength()); } private void updateLayoutRecursive(DrawableHitObject hitObject) @@ -271,12 +256,12 @@ namespace osu.Game.Rulesets.UI.Scrolling { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); break; } } @@ -295,19 +280,19 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (direction.Value) { case ScrollingDirection.Up: - hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; case ScrollingDirection.Down: - hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; case ScrollingDirection.Left: - hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; case ScrollingDirection.Right: - hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; } } From fd8e552a8bb12d2aa854adef2ab0d66ed7d5ea9b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 19:36:52 +0900 Subject: [PATCH 032/304] Fix filename not matching class name --- ...estSceneDrawableNote.cs => TestSceneDrawableManiaHitObject.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{TestSceneDrawableNote.cs => TestSceneDrawableManiaHitObject.cs} (100%) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs similarity index 100% rename from osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs From b9220d4dc728f681a16d6d4e664d4ae26a133951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 May 2021 15:57:57 +0200 Subject: [PATCH 033/304] Fix possible multiple enumeration --- .../UserInterface/TestSceneUpdateableBeatmapSetCover.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index ecb076d356..c928c0103f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -57,11 +57,13 @@ namespace osu.Game.Tests.Visual.UserInterface } }; - var coverTypes = Enum.GetValues(typeof(BeatmapSetCoverType)).Cast(); + var coverTypes = Enum.GetValues(typeof(BeatmapSetCoverType)) + .Cast() + .ToList(); for (int i = 0; i < 25; i++) { - var coverType = coverTypes.ElementAt(i % coverTypes.Count()); + var coverType = coverTypes[i % coverTypes.Count]; var cover = new UpdateableBeatmapSetCover(coverType) { From 36438175a0f61efc7c990ad8cf0f127cd09078bc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:04:58 +0900 Subject: [PATCH 034/304] Throw an exception if try to modify lifetime of PoolableDrawableWithLifetime without lifetime --- .../Pooling/PoolableDrawableWithLifetime.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index e94b6dca9d..d8565f4b30 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Diagnostics; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; @@ -31,7 +32,12 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeStart ?? double.MinValue; set { - if (Entry != null) Entry.LifetimeStart = value; + if (LifetimeStart == value) return; + + if (Entry == null) + throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); + + Entry.LifetimeStart = value; } } @@ -40,7 +46,12 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeEnd ?? double.MaxValue; set { - if (Entry != null) Entry.LifetimeEnd = value; + if (LifetimeEnd == value) return; + + if (Entry == null) + throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); + + Entry.LifetimeEnd = value; } } From 913fc8c3bc9eb4c37a0ff66bdccb5e90754dcf8a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:44:48 +0900 Subject: [PATCH 035/304] Revert the change of not adding non-pooled DHO to HOC until alive --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 78 +++++++++++++--------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 1d32313f2b..dcf350cbd4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.UI /// /// All alive entries and s used by the entries. /// - public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => drawableMap.Select(x => (x.Key, x.Value)); + public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.UI private readonly Dictionary startTimeMap = new Dictionary(); - private readonly Dictionary drawableMap = new Dictionary(); + private readonly Dictionary aliveDrawableMap = new Dictionary(); private readonly Dictionary nonPooledDrawableMap = new Dictionary(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); @@ -108,55 +108,70 @@ namespace osu.Game.Rulesets.UI public bool Remove(HitObjectLifetimeEntry entry) { if (!lifetimeManager.RemoveEntry(entry)) return false; - // The entry has to be removed from the non-pooled map here because non-pooled entry may be removed by specifying its entry. - nonPooledDrawableMap.Remove(entry); + + // This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry. + if (nonPooledDrawableMap.Remove(entry, out var drawable)) + removeDrawable(drawable); + allEntries.Remove(entry); return true; } - private void entryBecameAlive(LifetimeEntry entry) => addDrawable((HitObjectLifetimeEntry)entry); - - private void entryBecameDead(LifetimeEntry entry) => removeDrawable((HitObjectLifetimeEntry)entry); - - private void addDrawable(HitObjectLifetimeEntry entry) + private void entryBecameAlive(LifetimeEntry lifetimeEntry) { - Debug.Assert(!drawableMap.ContainsKey(entry)); + var entry = (HitObjectLifetimeEntry)lifetimeEntry; + Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); - nonPooledDrawableMap.TryGetValue(entry, out var drawable); + bool isNonPooled = nonPooledDrawableMap.TryGetValue(entry, out var drawable); drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); + aliveDrawableMap[entry] = drawable; + OnAdd(drawable); + + if (isNonPooled) return; + + addDrawable(drawable); + HitObjectUsageBegan?.Invoke(entry.HitObject); + } + + private void entryBecameDead(LifetimeEntry lifetimeEntry) + { + var entry = (HitObjectLifetimeEntry)lifetimeEntry; + Debug.Assert(aliveDrawableMap.ContainsKey(entry)); + + var drawable = aliveDrawableMap[entry]; + bool isNonPooled = nonPooledDrawableMap.ContainsKey(entry); + + drawable.OnKilled(); + aliveDrawableMap.Remove(entry); + OnRemove(drawable); + + if (isNonPooled) return; + + removeDrawable(drawable); + // The hit object is not freed when the DHO was not pooled. + HitObjectUsageFinished?.Invoke(entry.HitObject); + } + + private void addDrawable(DrawableHitObject drawable) + { drawable.OnNewResult += onNewResult; drawable.OnRevertResult += onRevertResult; bindStartTime(drawable); - AddInternal(drawableMap[entry] = drawable); - OnAdd(drawable); - - HitObjectUsageBegan?.Invoke(entry.HitObject); + AddInternal(drawable); } - private void removeDrawable(HitObjectLifetimeEntry entry) + private void removeDrawable(DrawableHitObject drawable) { - Debug.Assert(drawableMap.ContainsKey(entry)); - - var drawable = drawableMap[entry]; - - // OnKilled can potentially change the hitobject's result, so it needs to run first before unbinding. - drawable.OnKilled(); drawable.OnNewResult -= onNewResult; drawable.OnRevertResult -= onRevertResult; - drawableMap.Remove(entry); - - OnRemove(drawable); unbindStartTime(drawable); - RemoveInternal(drawable); - // The hit object is not freed when the DHO was not pooled. - if (!nonPooledDrawableMap.ContainsKey(entry)) - HitObjectUsageFinished?.Invoke(entry.HitObject); + RemoveInternal(drawable); } #endregion @@ -169,6 +184,7 @@ namespace osu.Game.Rulesets.UI throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated"); nonPooledDrawableMap.Add(drawable.Entry, drawable); + addDrawable(drawable); Add(drawable.Entry); } @@ -217,8 +233,10 @@ namespace osu.Game.Rulesets.UI public virtual void Clear() { lifetimeManager.ClearEntries(); + foreach (var drawable in nonPooledDrawableMap.Values) + removeDrawable(drawable); nonPooledDrawableMap.Clear(); - Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && drawableMap.Count == 0, "All hit objects should have been removed"); + Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed"); } protected override bool CheckChildrenLife() From 39bccc50489cf502c367891dc0a4071cec4c1dfc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:45:24 +0900 Subject: [PATCH 036/304] Revert "Adopt HitObjectContainer change in a test" This reverts commit f55aa016 --- osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs index 8f3d3f1276..f2bfccb6de 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs @@ -4,7 +4,6 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -20,14 +19,7 @@ namespace osu.Game.Tests.Gameplay [SetUp] public void Setup() => Schedule(() => { - Child = container = new HitObjectContainer - { - Clock = new FramedClock(new ManualClock - { - // Make sure hit objects with `StartTime == 0` are alive - CurrentTime = -1 - }) - }; + Child = container = new HitObjectContainer(); }); [Test] From 787bfd6bd08a4afaea8e937ab327445199572948 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:45:39 +0900 Subject: [PATCH 037/304] Revert "Fix failing taiko tests" This reverts commit 971ca398 --- .../TestSceneFlyingHits.cs | 16 ++++++++-------- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 8 ++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs index 5738be05d7..63854e7ead 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs @@ -20,19 +20,20 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase(HitType.Rim)] public void TestFlyingHits(HitType hitType) { + DrawableFlyingHit flyingHit = null; + AddStep("add flying hit", () => { addFlyingHit(hitType); - }); - AddAssert("hit type is correct", () => - { // flying hits all land in one common scrolling container (and stay there for rewind purposes), // so we need to manually get the latest one. - return this.ChildrenOfType() - .OrderByDescending(h => h.HitObject.StartTime) - .FirstOrDefault()?.HitObject.Type == hitType; + flyingHit = this.ChildrenOfType() + .OrderByDescending(h => h.HitObject.StartTime) + .FirstOrDefault(); }); + + AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType); } private void addFlyingHit(HitType hitType) @@ -41,8 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableDrumRollTick h; DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); - h.OnLoadComplete += _ => - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 06acdad3d6..87c936d386 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -129,12 +129,8 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableRuleset.Playfield.Add(h); - h.OnLoadComplete += _ => - { - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), - new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); - }; + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() From 4a93e27e8394ed5d347ca06989951319afb6e6c4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:46:30 +0900 Subject: [PATCH 038/304] Revert "Fix mania editor null reference" This reverts commit 1d023dce --- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 8f8f45c0dd..384f49d9b2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - // Overriding the base because this method is called right after `Column` is changed and `DrawableObject` is not yet loaded and Parent is not set. - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; - protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { From aa42cf2fc055eeee7a01188fbf61ef3735651dd3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:56:48 +0900 Subject: [PATCH 039/304] Fix setting lifetime during KeepAlive is ignored --- .../Pooling/PoolableDrawableWithLifetime.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index d8565f4b30..ed0430012a 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -32,12 +32,11 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeStart ?? double.MinValue; set { - if (LifetimeStart == value) return; - - if (Entry == null) + if (Entry == null && LifetimeStart != value) throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); - Entry.LifetimeStart = value; + if (Entry != null) + Entry.LifetimeStart = value; } } @@ -46,12 +45,11 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeEnd ?? double.MaxValue; set { - if (LifetimeEnd == value) return; - - if (Entry == null) + if (Entry == null && LifetimeEnd != value) throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); - Entry.LifetimeEnd = value; + if (Entry != null) + Entry.LifetimeEnd = value; } } From 040d393dd462931068601879ba7fc0b09f597136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 May 2021 21:00:12 +0200 Subject: [PATCH 040/304] Add visual test case for crossfade behaviour --- .../TestSceneUpdateableBeatmapSetCover.cs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index c928c0103f..4fef93e291 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -108,14 +108,52 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("no cover added", () => !updateableCover.ChildrenOfType().Any()); } + [Test] + public void TestCoverChangeOnNewBeatmap() + { + TestUpdateableBeatmapSetCover updateableCover = null; + BeatmapSetCover initialCover = null; + + AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover(0) + { + BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"), + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0.4f + }); + + AddUntilStep("cover loaded", () => updateableCover.ChildrenOfType().Any()); + AddStep("store initial cover", () => initialCover = updateableCover.ChildrenOfType().Single()); + AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1); + + AddStep("switch beatmap", + () => updateableCover.BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg")); + AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType().Except(new[] { initialCover }).Any()); + } + + private static BeatmapSetInfo createBeatmapWithCover(string coverUrl) => new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers { Cover = coverUrl } + } + }; + private class TestUpdateableBeatmapSetCover : UpdateableBeatmapSetCover { + private readonly int loadDelay; + + public TestUpdateableBeatmapSetCover(int loadDelay = 10000) + { + this.loadDelay = loadDelay; + } + protected override Drawable CreateDrawable(BeatmapSetInfo model) { if (model == null) return null; - return new TestBeatmapSetCover(model) + return new TestBeatmapSetCover(model, loadDelay) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -127,15 +165,18 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestBeatmapSetCover : BeatmapSetCover { - public TestBeatmapSetCover(BeatmapSetInfo set) + private readonly int loadDelay; + + public TestBeatmapSetCover(BeatmapSetInfo set, int loadDelay) : base(set) { + this.loadDelay = loadDelay; } [BackgroundDependencyLoader] private void load() { - Thread.Sleep(10000); + Thread.Sleep(loadDelay); } } } From 32b3ea70b966e4087d70d72f1306f55b4fa8b264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 May 2021 21:12:50 +0200 Subject: [PATCH 041/304] Fix both covers showing if cover is not fully opaque --- osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 3a4423e42e..b376690436 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -41,6 +41,10 @@ namespace osu.Game.Beatmaps.Drawables protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad); + // by default, ModelBackedDrawable hides the old drawable only after the new one has been fully loaded. + // this can lead to weird appearance if the cover is not fully opaque, so fade out as soon as a new load is requested in this particular case. + protected override void OnLoadStarted() => ApplyHideTransforms(DisplayedDrawable); + protected override Drawable CreateDrawable(BeatmapSetInfo model) { if (model == null) From 283488ea53f3c240ca1c856bb294ab8247a476d1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 03:36:30 +0300 Subject: [PATCH 042/304] Use `TransformImmediately` instead at beatmap listing search control Applies two changes: - Use `TransformImmediately` which achieves the same wanted transition behaviour without any issues. - Move the transition behaviour override into `BeatmapListingSearchControl` in a nested subclass of `UpdateableBeatmapSetCover`. --- osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs | 4 ---- .../Overlays/BeatmapListing/BeatmapListingSearchControl.cs | 7 ++++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index b376690436..3a4423e42e 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -41,10 +41,6 @@ namespace osu.Game.Beatmaps.Drawables protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad); - // by default, ModelBackedDrawable hides the old drawable only after the new one has been fully loaded. - // this can lead to weird appearance if the cover is not fully opaque, so fade out as soon as a new load is requested in this particular case. - protected override void OnLoadStarted() => ApplyHideTransforms(DisplayedDrawable); - protected override Drawable CreateDrawable(BeatmapSetInfo model) { if (model == null) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 1576431d40..97ccb66599 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.BeatmapListing { RelativeSizeAxes = Axes.Both, Masking = true, - Child = beatmapCover = new UpdateableBeatmapSetCover + Child = beatmapCover = new TopSearchBeatmapSetCover { RelativeSizeAxes = Axes.Both, Alpha = 0, @@ -184,5 +184,10 @@ namespace osu.Game.Overlays.BeatmapListing return true; } } + + private class TopSearchBeatmapSetCover : UpdateableBeatmapSetCover + { + protected override bool TransformImmediately => true; + } } } From c7325f0f775357a51f8d1c9963c825f8fc32cd2a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 08:07:12 +0300 Subject: [PATCH 043/304] Add missing load delay That was a bad one... `ModelBackedDrawable` has a default of 0ms load delay, while previously the wrapper created for beatmap set cover used default 500ms, this change is bringing the load delay back, to avoid firing hundreds of web requests just when doing a quick long scroll on beatmap listing. --- osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 3a4423e42e..7248c9213c 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -36,7 +36,9 @@ namespace osu.Game.Beatmaps.Drawables }; } - protected override double TransformDuration => 400.0; + protected override double LoadDelay => 500; + + protected override double TransformDuration => 400; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad); From 7ca3e13712b974ce4cee0b431602ddf2fa654eed Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 07:43:01 +0300 Subject: [PATCH 044/304] Implement basic years panel --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 34 ++++++ osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 108 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs create mode 100644 osu.Game/Overlays/News/Sidebar/YearsPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs new file mode 100644 index 0000000000..75975f04f8 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Overlays.News.Sidebar; +using osu.Framework.Allocation; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsYearsPanel : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private readonly YearsPanel panel; + + public TestSceneNewsYearsPanel() + { + Add(panel = new YearsPanel() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + AddStep("Load years", () => panel.Years = new[] { 1000, 2000, 3000, 4000 }); + AddStep("Load different years", () => panel.Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001 }); + } + } +} diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs new file mode 100644 index 0000000000..d71c7ba48e --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -0,0 +1,108 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using System.Linq; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using System.Collections.Generic; +using osu.Game.Graphics; +using osu.Framework.Bindables; +using System.Collections.Specialized; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class YearsPanel : CompositeDrawable + { + public int[] Years + { + set + { + years.Clear(); + years.AddRange(value); + } + } + + private readonly BindableList years = new BindableList(); + + private FillFlowContainer flow; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Width = 160; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 6; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3 + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), + Child = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(5) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + years.BindCollectionChanged((u, v) => + { + switch (v.Action) + { + case NotifyCollectionChangedAction.Add: + flow.Children = years.Select(y => new YearButton(y)).ToArray(); + break; + } + }, true); + } + + private class YearButton : OsuHoverContainer + { + protected override IEnumerable EffectTargets => new[] { text }; + + private readonly int year; + private readonly OsuSpriteText text; + + public YearButton(int year) + { + this.year = year; + + Size = new Vector2(33.75f, 15); + Child = text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12), + Text = year.ToString() + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + Action = () => { }; // TODO + } + } + } +} From 7971a2ef485bf8e80104a2032c378da069acdb7f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 08:47:00 +0300 Subject: [PATCH 045/304] Implement MonthPanel component --- .../Visual/Online/TestSceneNewsMonthPanel.cs | 80 +++++++++ osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 155 ++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs create mode 100644 osu.Game/Overlays/News/Sidebar/MonthPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs new file mode 100644 index 0000000000..75e02b66e1 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.News.Sidebar; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsMonthPanel : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + [Test] + public void CreateClosedMonthPanel() + { + AddStep("Create", () => Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }, + new MonthPanel(DateTime.Now, posts), + } + }); + } + + [Test] + public void CreateOpenMonthPanel() + { + AddStep("Create", () => Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }, + new MonthPanel(DateTime.Now, posts) + { + IsOpen = { Value = true } + }, + } + }); + } + + private static APINewsPost[] posts => new[] + { + new APINewsPost + { + Title = "Short title" + }, + new APINewsPost + { + Title = "Oh boy that's a long post title I wonder if it will break anything" + }, + new APINewsPost + { + Title = "Medium title, nothing to see here" + } + }; + } +} diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs new file mode 100644 index 0000000000..1dd1c561ab --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -0,0 +1,155 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Graphics.Containers; +using osuTK; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using System.Linq; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class MonthPanel : CompositeDrawable + { + public readonly BindableBool IsOpen = new BindableBool(); + + private readonly FillFlowContainer postsFlow; + + public MonthPanel(DateTime date, APINewsPost[] posts) + { + Width = 160; + AutoSizeDuration = 250; + AutoSizeEasing = Easing.OutQuint; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new DropdownButton(date) + { + IsOpen = { BindTarget = IsOpen } + }, + postsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = posts.Select(p => new PostButton(p)).ToArray() + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsOpen.BindValueChanged(open => + { + ClearTransforms(); + + if (open.NewValue) + { + AutoSizeAxes = Axes.Y; + postsFlow.FadeIn(250, Easing.OutQuint); + } + else + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(15, 250, Easing.OutQuint); + + postsFlow.FadeOut(250, Easing.OutQuint); + } + }, true); + + // First state change should be instant. + FinishTransforms(); + postsFlow.FinishTransforms(); + } + + private class DropdownButton : OsuHoverContainer + { + public readonly BindableBool IsOpen = new BindableBool(); + + protected override IEnumerable EffectTargets => null; + + private readonly SpriteIcon icon; + + public DropdownButton(DateTime date) + { + Size = new Vector2(160, 15); + Action = IsOpen.Toggle; + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = date.ToString("MMM yyyy") + }, + icon = new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(10), + Icon = FontAwesome.Solid.ChevronDown + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsOpen.BindValueChanged(open => + { + icon.Scale = new Vector2(1, open.NewValue ? -1 : 1); + }, true); + } + } + + private class PostButton : OsuHoverContainer + { + protected override IEnumerable EffectTargets => new[] { text }; + + private readonly APINewsPost post; + private readonly TextFlowContainer text; + + public PostButton(APINewsPost post) + { + this.post = post; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = post.Title + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + Action = () => { }; // TODO + } + } + } +} From 4b972249320ad7832619a999679e1a6558a9e7bf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 09:53:52 +0300 Subject: [PATCH 046/304] Implement NewsSideBar component --- .../Visual/Online/TestSceneNewsSideBar.cs | 114 ++++++++++++++++++ .../Online/API/Requests/GetNewsResponse.cs | 3 + .../API/Requests/Responses/APINewsSidebar.cs | 20 +++ osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 114 ++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs create mode 100644 osu.Game/Overlays/News/Sidebar/NewsSideBar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs new file mode 100644 index 0000000000..931341f837 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -0,0 +1,114 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.News.Sidebar; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsSideBar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private NewsSideBar sidebar; + + [Test] + public void TestCreateEmpty() + { + createSidebar(null); + } + + [Test] + public void TestCreateWithData() + { + createSidebar(metadata); + } + + [Test] + public void TestDataChange() + { + createSidebar(null); + AddStep("Add data", () => + { + if (sidebar != null) + sidebar.Metadata.Value = metadata; + }); + } + + private void createSidebar(APINewsSidebar metadata) => AddStep("Create", () => Child = sidebar = new NewsSideBar + { + Metadata = { Value = metadata } + }); + + private static APINewsSidebar metadata = new APINewsSidebar + { + CurrentYear = 2021, + Years = new[] + { + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = new List + { + new APINewsPost + { + Title = "(Mar) Short title", + PublishedAt = new DateTime(2021, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2021, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Medium title, nothing to see here", + PublishedAt = new DateTime(2021, 3, 1) + }, + new APINewsPost + { + Title = "(Feb) Short title", + PublishedAt = new DateTime(2021, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2021, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Medium title, nothing to see here", + PublishedAt = new DateTime(2021, 2, 1) + }, + new APINewsPost + { + Title = "Short title", + PublishedAt = new DateTime(2021, 1, 1) + }, + new APINewsPost + { + Title = "Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2021, 1, 1) + }, + new APINewsPost + { + Title = "Medium title, nothing to see here", + PublishedAt = new DateTime(2021, 1, 1) + } + } + }; + } +} diff --git a/osu.Game/Online/API/Requests/GetNewsResponse.cs b/osu.Game/Online/API/Requests/GetNewsResponse.cs index 835289a51d..98f76d105c 100644 --- a/osu.Game/Online/API/Requests/GetNewsResponse.cs +++ b/osu.Game/Online/API/Requests/GetNewsResponse.cs @@ -11,5 +11,8 @@ namespace osu.Game.Online.API.Requests { [JsonProperty("news_posts")] public IEnumerable NewsPosts; + + [JsonProperty("news_sidebar")] + public APINewsSidebar SidebarMetadata; } } diff --git a/osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs b/osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs new file mode 100644 index 0000000000..b8d6469a1d --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APINewsSidebar + { + [JsonProperty("current_year")] + public int CurrentYear { get; set; } + + [JsonProperty("news_posts")] + public IEnumerable NewsPosts { get; set; } + + [JsonProperty("years")] + public int[] Years { get; set; } + } +} diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs new file mode 100644 index 0000000000..34c995cab6 --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -0,0 +1,114 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osuTK; +using System.Collections.Generic; +using System; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class NewsSideBar : CompositeDrawable + { + public readonly Bindable Metadata = new Bindable(); + + private YearsPanel yearsPanel; + private FillFlowContainer monthsFlow; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Y; + Width = 250; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Top = 20, + Left = 50, + Right = 30 + }, + Child = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + yearsPanel = new YearsPanel(), + monthsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(metadata => + { + monthsFlow.Clear(); + + if (metadata.NewValue == null) + { + yearsPanel.Hide(); + return; + } + + yearsPanel.Years = metadata.NewValue.Years; + yearsPanel.Show(); + + if (metadata.NewValue != null) + { + var dict = new Dictionary>(); + + foreach (var p in metadata.NewValue.NewsPosts) + { + var month = p.PublishedAt.Month; + + if (dict.ContainsKey(month)) + dict[month].Add(p); + else + { + dict.Add(month, new List(new[] { p })); + } + } + + bool isFirst = true; + + foreach (var keyValuePair in dict) + { + monthsFlow.Add(new MonthPanel(new DateTime(metadata.NewValue.CurrentYear, keyValuePair.Key, 1), keyValuePair.Value.ToArray()) + { + IsOpen = { Value = isFirst } + }); + + isFirst = false; + } + } + }, true); + } + } +} From 0d243be457f514eab35edd8a3499f51fd0f573c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 10:07:43 +0300 Subject: [PATCH 047/304] CI fixes --- osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs | 2 +- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 3 --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 3 --- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 931341f837..96161c28ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online Metadata = { Value = metadata } }); - private static APINewsSidebar metadata = new APINewsSidebar + private static readonly APINewsSidebar metadata = new APINewsSidebar { CurrentYear = 2021, Years = new[] diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 75975f04f8..446cd06eea 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneNewsYearsPanel() { - Add(panel = new YearsPanel() + Add(panel = new YearsPanel { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 1dd1c561ab..8b405eae10 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -125,13 +125,10 @@ namespace osu.Game.Overlays.News.Sidebar { protected override IEnumerable EffectTargets => new[] { text }; - private readonly APINewsPost post; private readonly TextFlowContainer text; public PostButton(APINewsPost post) { - this.post = post; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index d71c7ba48e..046e1804bd 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -79,13 +79,10 @@ namespace osu.Game.Overlays.News.Sidebar { protected override IEnumerable EffectTargets => new[] { text }; - private readonly int year; private readonly OsuSpriteText text; public YearButton(int year) { - this.year = year; - Size = new Vector2(33.75f, 15); Child = text = new OsuSpriteText { From 220eef035181e3d08b0fde4430b966b3f69c3cb7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 17:00:18 +0300 Subject: [PATCH 048/304] Remove overcomplicated date logic in MonthPanel --- .../Visual/Online/TestSceneNewsMonthPanel.cs | 10 ++++++---- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 6 +++--- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 3 +-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs index 75e02b66e1..ee7fb8b407 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(DateTime.Now, posts), + new MonthPanel(posts), } }); } @@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(DateTime.Now, posts) + new MonthPanel(posts) { IsOpen = { Value = true } }, @@ -61,11 +62,12 @@ namespace osu.Game.Tests.Visual.Online }); } - private static APINewsPost[] posts => new[] + private static List posts => new List { new APINewsPost { - Title = "Short title" + Title = "Short title", + PublishedAt = DateTimeOffset.Now }, new APINewsPost { diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 8b405eae10..5f2acd63d1 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthPanel(DateTime date, APINewsPost[] posts) + public MonthPanel(List posts) { Width = 160; AutoSizeDuration = 250; @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownButton(date) + new DropdownButton(posts[0].PublishedAt) { IsOpen = { BindTarget = IsOpen } }, @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly SpriteIcon icon; - public DropdownButton(DateTime date) + public DropdownButton(DateTimeOffset date) { Size = new Vector2(160, 15); Action = IsOpen.Toggle; diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 34c995cab6..9a2fdf2d97 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -9,7 +9,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Framework.Graphics.Shapes; using osuTK; using System.Collections.Generic; -using System; namespace osu.Game.Overlays.News.Sidebar { @@ -100,7 +99,7 @@ namespace osu.Game.Overlays.News.Sidebar foreach (var keyValuePair in dict) { - monthsFlow.Add(new MonthPanel(new DateTime(metadata.NewValue.CurrentYear, keyValuePair.Key, 1), keyValuePair.Value.ToArray()) + monthsFlow.Add(new MonthPanel(keyValuePair.Value) { IsOpen = { Value = isFirst } }); From 69f01e82db6cf14052f6fe05d063619042449b86 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 14:42:56 +0300 Subject: [PATCH 049/304] Add bottom padding for NewsSideBar content --- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 9a2fdf2d97..4444cc79b8 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Top = 20, + Vertical = 20, Left = 50, Right = 30 }, From 711e7ba860cd0e3e2113fa09ed53b79c6e6a07e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 14:43:23 +0300 Subject: [PATCH 050/304] Apply suggestions for MonthPanel --- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 37 ++++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 5f2acd63d1..4d7a5f18aa 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -19,6 +19,9 @@ namespace osu.Game.Overlays.News.Sidebar { public class MonthPanel : CompositeDrawable { + private const int header_height = 15; + private const int animation_duration = 250; + public readonly BindableBool IsOpen = new BindableBool(); private readonly FillFlowContainer postsFlow; @@ -26,8 +29,7 @@ namespace osu.Game.Overlays.News.Sidebar public MonthPanel(List posts) { Width = 160; - AutoSizeDuration = 250; - AutoSizeEasing = Easing.OutQuint; + Masking = true; InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -63,33 +65,46 @@ namespace osu.Game.Overlays.News.Sidebar if (open.NewValue) { AutoSizeAxes = Axes.Y; - postsFlow.FadeIn(250, Easing.OutQuint); + postsFlow.FadeIn(animation_duration, Easing.OutQuint); } else { AutoSizeAxes = Axes.None; - this.ResizeHeightTo(15, 250, Easing.OutQuint); + this.ResizeHeightTo(header_height, animation_duration, Easing.OutQuint); - postsFlow.FadeOut(250, Easing.OutQuint); + postsFlow.FadeOut(animation_duration, Easing.OutQuint); } }, true); // First state change should be instant. - FinishTransforms(); - postsFlow.FinishTransforms(); + FinishTransforms(true); } - private class DropdownButton : OsuHoverContainer + private bool shouldUpdateAutosize = true; + + // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. + protected override void UpdateAfterAutoSize() + { + base.UpdateAfterAutoSize(); + + if (shouldUpdateAutosize) + { + AutoSizeDuration = animation_duration; + AutoSizeEasing = Easing.OutQuint; + + shouldUpdateAutosize = false; + } + } + + private class DropdownButton : OsuClickableContainer { public readonly BindableBool IsOpen = new BindableBool(); - protected override IEnumerable EffectTargets => null; - private readonly SpriteIcon icon; public DropdownButton(DateTimeOffset date) { - Size = new Vector2(160, 15); + Size = new Vector2(160, header_height); Action = IsOpen.Toggle; Children = new Drawable[] { From e736240a064020b4f7b767acfb278ca15d02f3c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:12:04 +0300 Subject: [PATCH 051/304] Use lookup instead of dictionary to distribute posts --- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 4 +-- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 29 +++++-------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 4d7a5f18aa..5460ccce16 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthPanel(List posts) + public MonthPanel(IEnumerable posts) { Width = 160; Masking = true; @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownButton(posts[0].PublishedAt) + new DropdownButton(posts.ElementAt(0).PublishedAt) { IsOpen = { BindTarget = IsOpen } }, diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 4444cc79b8..4d2d3949bd 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Graphics.Shapes; using osuTK; -using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.News.Sidebar { @@ -81,30 +81,17 @@ namespace osu.Game.Overlays.News.Sidebar if (metadata.NewValue != null) { - var dict = new Dictionary>(); + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); - foreach (var p in metadata.NewValue.NewsPosts) + var keys = lookup.Select(kvp => kvp.Key); + var sortedKeys = keys.OrderByDescending(k => k).ToList(); + + for (int i = 0; i < sortedKeys.Count; i++) { - var month = p.PublishedAt.Month; - - if (dict.ContainsKey(month)) - dict[month].Add(p); - else + monthsFlow.Add(new MonthPanel(lookup[sortedKeys[i]]) { - dict.Add(month, new List(new[] { p })); - } - } - - bool isFirst = true; - - foreach (var keyValuePair in dict) - { - monthsFlow.Add(new MonthPanel(keyValuePair.Value) - { - IsOpen = { Value = isFirst } + IsOpen = { Value = i == 0 } }); - - isFirst = false; } } }, true); From 9603712aa1a21b930179533f788e36448c9ec8a1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:33:27 +0300 Subject: [PATCH 052/304] Cache metadata in NewsSideBar --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 32 ++++++++++++++----- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 13 ++------ osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 29 +++++++---------- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 446cd06eea..031a98dd8b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -5,6 +5,9 @@ using osu.Framework.Graphics; using osu.Game.Overlays.News.Sidebar; using osu.Framework.Allocation; using osu.Game.Overlays; +using osu.Framework.Bindables; +using osu.Game.Online.API.Requests.Responses; +using NUnit.Framework; namespace osu.Game.Tests.Visual.Online { @@ -13,22 +16,35 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private readonly YearsPanel panel; + [Cached] + private readonly Bindable metadataBindable = new Bindable(); - public TestSceneNewsYearsPanel() + private YearsPanel panel; + + [SetUp] + public void SetUp() { - Add(panel = new YearsPanel + Child = panel = new YearsPanel { Anchor = Anchor.Centre, Origin = Anchor.Centre - }); + }; } - protected override void LoadComplete() + [Test] + public void TestMetadata() { - base.LoadComplete(); - AddStep("Load years", () => panel.Years = new[] { 1000, 2000, 3000, 4000 }); - AddStep("Load different years", () => panel.Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001 }); + AddStep("Change metadata to null", () => metadataBindable.Value = null); + AddAssert("Panel is hidden", () => panel.IsPresent == false); + AddStep("Change metadata", () => metadataBindable.Value = metadata); + AddAssert("Panel is visible", () => panel.IsPresent == true); + AddStep("Change metadata to null", () => metadataBindable.Value = null); + AddAssert("Panel is hidden", () => panel.IsPresent == false); } + + private static readonly APINewsSidebar metadata = new APINewsSidebar + { + Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001, 9001 } + }; } } diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 4d2d3949bd..75726c5fed 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -14,9 +14,9 @@ namespace osu.Game.Overlays.News.Sidebar { public class NewsSideBar : CompositeDrawable { + [Cached] public readonly Bindable Metadata = new Bindable(); - private YearsPanel yearsPanel; private FillFlowContainer monthsFlow; [BackgroundDependencyLoader] @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 20), Children = new Drawable[] { - yearsPanel = new YearsPanel(), + new YearsPanel(), monthsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -70,15 +70,6 @@ namespace osu.Game.Overlays.News.Sidebar { monthsFlow.Clear(); - if (metadata.NewValue == null) - { - yearsPanel.Hide(); - return; - } - - yearsPanel.Years = metadata.NewValue.Years; - yearsPanel.Show(); - if (metadata.NewValue != null) { var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 046e1804bd..23dd8d8a5e 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -12,28 +12,21 @@ using osu.Game.Graphics.Sprites; using System.Collections.Generic; using osu.Game.Graphics; using osu.Framework.Bindables; -using System.Collections.Specialized; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.News.Sidebar { public class YearsPanel : CompositeDrawable { - public int[] Years - { - set - { - years.Clear(); - years.AddRange(value); - } - } - - private readonly BindableList years = new BindableList(); + private readonly Bindable metadata = new Bindable(); private FillFlowContainer flow; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, Bindable metadata) { + this.metadata.BindTo(metadata); + Width = 160; AutoSizeAxes = Axes.Y; Masking = true; @@ -64,14 +57,16 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - years.BindCollectionChanged((u, v) => + metadata.BindValueChanged(m => { - switch (v.Action) + if (m.NewValue == null) { - case NotifyCollectionChangedAction.Add: - flow.Children = years.Select(y => new YearButton(y)).ToArray(); - break; + Hide(); + return; } + + flow.Children = m.NewValue.Years.Select(y => new YearButton(y)).ToArray(); + Show(); }, true); } From 0a9c3c9413e89efaf183793fc781e3bcc7f4a5bc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:39:50 +0300 Subject: [PATCH 053/304] Move metadata change logic to it's own method --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 2 +- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 43 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 031a98dd8b..014a9ac7ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestMetadata() + public void TestVisibility() { AddStep("Change metadata to null", () => metadataBindable.Value = null); AddAssert("Panel is hidden", () => panel.IsPresent == false); diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 75726c5fed..d90b73aa82 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -66,26 +66,35 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - Metadata.BindValueChanged(metadata => + Metadata.BindValueChanged(onMetadataChanged, true); + } + + private void onMetadataChanged(ValueChangedEvent metadata) + { + monthsFlow.Clear(); + + if (metadata.NewValue == null) + return; + + var allPosts = metadata.NewValue.NewsPosts; + + if (!allPosts?.Any() ?? false) + return; + + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); + + var keys = lookup.Select(kvp => kvp.Key); + var sortedKeys = keys.OrderByDescending(k => k).ToList(); + + for (int i = 0; i < sortedKeys.Count; i++) { - monthsFlow.Clear(); + var posts = lookup[sortedKeys[i]]; - if (metadata.NewValue != null) + monthsFlow.Add(new MonthPanel(posts) { - var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); - - var keys = lookup.Select(kvp => kvp.Key); - var sortedKeys = keys.OrderByDescending(k => k).ToList(); - - for (int i = 0; i < sortedKeys.Count; i++) - { - monthsFlow.Add(new MonthPanel(lookup[sortedKeys[i]]) - { - IsOpen = { Value = i == 0 } - }); - } - } - }, true); + IsOpen = { Value = i == 0 } + }); + } } } } From 705aad262aa8c2e4ea798a79306d2df820693a61 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:42:40 +0300 Subject: [PATCH 054/304] Rename MonthPanel to MonthDropdown --- ...SceneNewsMonthPanel.cs => TestSceneNewsMonthDropdown.cs} | 6 +++--- .../News/Sidebar/{MonthPanel.cs => MonthDropdown.cs} | 4 ++-- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneNewsMonthPanel.cs => TestSceneNewsMonthDropdown.cs} (94%) rename osu.Game/Overlays/News/Sidebar/{MonthPanel.cs => MonthDropdown.cs} (97%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs similarity index 94% rename from osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs rename to osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs index ee7fb8b407..c51e299f78 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays.News.Sidebar; namespace osu.Game.Tests.Visual.Online { - public class TestSceneNewsMonthPanel : OsuTestScene + public class TestSceneNewsMonthDropdown : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(posts), + new MonthDropdown(posts), } }); } @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(posts) + new MonthDropdown(posts) { IsOpen = { Value = true } }, diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs similarity index 97% rename from osu.Game/Overlays/News/Sidebar/MonthPanel.cs rename to osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 5460ccce16..87a72a3eaf 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -17,7 +17,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.News.Sidebar { - public class MonthPanel : CompositeDrawable + public class MonthDropdown : CompositeDrawable { private const int header_height = 15; private const int animation_duration = 250; @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthPanel(IEnumerable posts) + public MonthDropdown(IEnumerable posts) { Width = 160; Masking = true; diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index d90b73aa82..1518d61d59 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.News.Sidebar [Cached] public readonly Bindable Metadata = new Bindable(); - private FillFlowContainer monthsFlow; + private FillFlowContainer monthsFlow; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.News.Sidebar Children = new Drawable[] { new YearsPanel(), - monthsFlow = new FillFlowContainer + monthsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.News.Sidebar { var posts = lookup[sortedKeys[i]]; - monthsFlow.Add(new MonthPanel(posts) + monthsFlow.Add(new MonthDropdown(posts) { IsOpen = { Value = i == 0 } }); From 01f5c77dac4b5705e78ba6ec10b2d864ea92073f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:56:50 +0300 Subject: [PATCH 055/304] Add better comments explaining empty actions --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 2 +- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 87a72a3eaf..ff50bfe4c2 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -160,7 +160,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // TODO + Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } } diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 23dd8d8a5e..c06a8424f6 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // TODO + Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } } From 208224cc0deb0ae2d79c155f04073577332f322f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:08:09 +0300 Subject: [PATCH 056/304] CI fixes --- osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs | 6 +++--- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 014a9ac7ed..39bb97fe98 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -35,11 +35,11 @@ namespace osu.Game.Tests.Visual.Online public void TestVisibility() { AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => panel.IsPresent == false); + AddAssert("Panel is hidden", () => !panel.IsPresent); AddStep("Change metadata", () => metadataBindable.Value = metadata); - AddAssert("Panel is visible", () => panel.IsPresent == true); + AddAssert("Panel is visible", () => panel.IsPresent); AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => panel.IsPresent == false); + AddAssert("Panel is hidden", () => !panel.IsPresent); } private static readonly APINewsSidebar metadata = new APINewsSidebar diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index ff50bfe4c2..35092acdc1 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -160,7 +160,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // Avoid button being disabled since there's no proper action assigned. + Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } } From 1c0b0996cf43249e4127238f39965133d65c9d4f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:42:18 +0300 Subject: [PATCH 057/304] Rename DropdownButton to DropdownHeader --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 35092acdc1..cc06f48544 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownButton(posts.ElementAt(0).PublishedAt) + new DropdownHeader(posts.ElementAt(0).PublishedAt) { IsOpen = { BindTarget = IsOpen } }, @@ -96,15 +96,16 @@ namespace osu.Game.Overlays.News.Sidebar } } - private class DropdownButton : OsuClickableContainer + private class DropdownHeader : OsuClickableContainer { public readonly BindableBool IsOpen = new BindableBool(); private readonly SpriteIcon icon; - public DropdownButton(DateTimeOffset date) + public DropdownHeader(DateTimeOffset date) { - Size = new Vector2(160, header_height); + RelativeSizeAxes = Axes.X; + Height = header_height; Action = IsOpen.Toggle; Children = new Drawable[] { From c2ba16f977ba8e3fb04d72f56bb81cd647def6d2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:51:59 +0300 Subject: [PATCH 058/304] Use relative sizing for MonthDropdown --- .../Online/TestSceneNewsMonthDropdown.cs | 55 ++++++++----------- .../Overlays/News/Sidebar/MonthDropdown.cs | 2 +- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 8 +-- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs index c51e299f78..f03b7d3f58 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs @@ -22,46 +22,35 @@ namespace osu.Game.Tests.Visual.Online [Test] public void CreateClosedMonthPanel() { - AddStep("Create", () => Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2, - }, - new MonthDropdown(posts), - } - }); + create(false); } [Test] public void CreateOpenMonthPanel() { - AddStep("Create", () => Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2, - }, - new MonthDropdown(posts) - { - IsOpen = { Value = true } - }, - } - }); + create(true); } + private void create(bool isOpen) => AddStep("Create", () => Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + Width = 160, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }, + new MonthDropdown(posts) + { + IsOpen = { Value = isOpen } + } + } + }); + private static List posts => new List { new APINewsPost diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index cc06f48544..11c0ce863f 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.News.Sidebar public MonthDropdown(IEnumerable posts) { - Width = 160; + RelativeSizeAxes = Axes.X; Masking = true; InternalChild = new FillFlowContainer { diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 1518d61d59..7de7e4dd71 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -42,17 +42,17 @@ namespace osu.Game.Overlays.News.Sidebar }, Child = new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Spacing = new Vector2(0, 20), Children = new Drawable[] { new YearsPanel(), monthsFlow = new FillFlowContainer { - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 10) } From b79a0237a3b57104df4dfee944ad1d2033af49ce Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:54:19 +0300 Subject: [PATCH 059/304] Fix TestSceneNewsYearsPanel error --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 39bb97fe98..3199b83a7d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -22,24 +22,21 @@ namespace osu.Game.Tests.Visual.Online private YearsPanel panel; [SetUp] - public void SetUp() + public void SetUp() => Schedule(() => Child = panel = new YearsPanel { - Child = panel = new YearsPanel - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }; - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); [Test] public void TestVisibility() { AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => !panel.IsPresent); + AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); AddStep("Change metadata", () => metadataBindable.Value = metadata); - AddAssert("Panel is visible", () => panel.IsPresent); + AddUntilStep("Panel is visible", () => panel?.Alpha == 1); AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => !panel.IsPresent); + AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); } private static readonly APINewsSidebar metadata = new APINewsSidebar From 822d99e69f92f1ef07b04f242219210eaf0c9971 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 20:42:13 +0300 Subject: [PATCH 060/304] Remove pointless test scenes --- .../Online/TestSceneNewsMonthDropdown.cs | 71 ------------------- .../Visual/Online/TestSceneNewsSideBar.cs | 31 +++----- .../Visual/Online/TestSceneNewsYearsPanel.cs | 47 ------------ 3 files changed, 9 insertions(+), 140 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs deleted file mode 100644 index f03b7d3f58..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs +++ /dev/null @@ -1,71 +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 System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; -using osu.Game.Overlays.News.Sidebar; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneNewsMonthDropdown : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - [Test] - public void CreateClosedMonthPanel() - { - create(false); - } - - [Test] - public void CreateOpenMonthPanel() - { - create(true); - } - - private void create(bool isOpen) => AddStep("Create", () => Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Y, - Width = 160, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2, - }, - new MonthDropdown(posts) - { - IsOpen = { Value = isOpen } - } - } - }); - - private static List posts => new List - { - new APINewsPost - { - Title = "Short title", - PublishedAt = DateTimeOffset.Now - }, - new APINewsPost - { - Title = "Oh boy that's a long post title I wonder if it will break anything" - }, - new APINewsPost - { - Title = "Medium title, nothing to see here" - } - }; - } -} diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 96161c28ed..8b09a3d176 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.News.Sidebar; @@ -18,33 +20,18 @@ namespace osu.Game.Tests.Visual.Online private NewsSideBar sidebar; - [Test] - public void TestCreateEmpty() - { - createSidebar(null); - } + [SetUp] + public void SetUp() => Schedule(() => Child = sidebar = new NewsSideBar()); [Test] - public void TestCreateWithData() + public void TestMetadataChange() { - createSidebar(metadata); + AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); + AddStep("Add data", () => sidebar.Metadata.Value = metadata); + AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } - [Test] - public void TestDataChange() - { - createSidebar(null); - AddStep("Add data", () => - { - if (sidebar != null) - sidebar.Metadata.Value = metadata; - }); - } - - private void createSidebar(APINewsSidebar metadata) => AddStep("Create", () => Child = sidebar = new NewsSideBar - { - Metadata = { Value = metadata } - }); + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); private static readonly APINewsSidebar metadata = new APINewsSidebar { diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs deleted file mode 100644 index 3199b83a7d..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Game.Overlays.News.Sidebar; -using osu.Framework.Allocation; -using osu.Game.Overlays; -using osu.Framework.Bindables; -using osu.Game.Online.API.Requests.Responses; -using NUnit.Framework; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneNewsYearsPanel : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - [Cached] - private readonly Bindable metadataBindable = new Bindable(); - - private YearsPanel panel; - - [SetUp] - public void SetUp() => Schedule(() => Child = panel = new YearsPanel - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }); - - [Test] - public void TestVisibility() - { - AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); - AddStep("Change metadata", () => metadataBindable.Value = metadata); - AddUntilStep("Panel is visible", () => panel?.Alpha == 1); - AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); - } - - private static readonly APINewsSidebar metadata = new APINewsSidebar - { - Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001, 9001 } - }; - } -} From b0297c6324cff8807952194154271261f4155b4a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 20:52:11 +0300 Subject: [PATCH 061/304] Fix incorrect no posts handling and add corresponding test --- .../Visual/Online/TestSceneNewsSideBar.cs | 28 ++++++++++++++++++- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 8b09a3d176..b5405a979e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -24,13 +24,20 @@ namespace osu.Game.Tests.Visual.Online public void SetUp() => Schedule(() => Child = sidebar = new NewsSideBar()); [Test] - public void TestMetadataChange() + public void TestYearsPanelVisibility() { AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); AddStep("Add data", () => sidebar.Metadata.Value = metadata); AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } + [Test] + public void TestMetadataWithNoPosts() + { + AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + } + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); private static readonly APINewsSidebar metadata = new APINewsSidebar @@ -97,5 +104,24 @@ namespace osu.Game.Tests.Visual.Online } } }; + + private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar + { + CurrentYear = 2022, + Years = new[] + { + 2022, + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = Array.Empty() + }; } } diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 7de7e4dd71..baa1826185 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.News.Sidebar var allPosts = metadata.NewValue.NewsPosts; - if (!allPosts?.Any() ?? false) + if (!allPosts?.Any() ?? true) return; var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); From f4801c08ff9693c8e97a5252b96a38fe6f288760 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 22:34:01 +0300 Subject: [PATCH 062/304] Refactor MonthDropdown to ensure all the posts are within a given month --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 11 ++++++++--- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 11c0ce863f..91412d9527 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using System.Diagnostics; namespace osu.Game.Overlays.News.Sidebar { @@ -26,8 +27,10 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthDropdown(IEnumerable posts) + public MonthDropdown(int month, int year, IEnumerable posts) { + Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); + RelativeSizeAxes = Axes.X; Masking = true; InternalChild = new FillFlowContainer @@ -38,7 +41,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownHeader(posts.ElementAt(0).PublishedAt) + new DropdownHeader(month, year) { IsOpen = { BindTarget = IsOpen } }, @@ -102,8 +105,10 @@ namespace osu.Game.Overlays.News.Sidebar private readonly SpriteIcon icon; - public DropdownHeader(DateTimeOffset date) + public DropdownHeader(int month, int year) { + var date = new DateTime(year, month, 1); + RelativeSizeAxes = Axes.X; Height = header_height; Action = IsOpen.Toggle; diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index baa1826185..3851dea83a 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -86,11 +86,14 @@ namespace osu.Game.Overlays.News.Sidebar var keys = lookup.Select(kvp => kvp.Key); var sortedKeys = keys.OrderByDescending(k => k).ToList(); + var year = metadata.NewValue.CurrentYear; + for (int i = 0; i < sortedKeys.Count; i++) { - var posts = lookup[sortedKeys[i]]; + var month = sortedKeys[i]; + var posts = lookup[month]; - monthsFlow.Add(new MonthDropdown(posts) + monthsFlow.Add(new MonthDropdown(month, year, posts) { IsOpen = { Value = i == 0 } }); From 20a6903a40e1a59a253714780718192e1f195837 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 23:43:01 +0300 Subject: [PATCH 063/304] Use GridContainer to distribute buttons in YearsPanel --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 93 +++++++++++++++++--- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index c06a8424f6..2a4e2024d2 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -5,14 +5,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osuTK; -using System.Linq; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using System.Collections.Generic; using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; +using System; namespace osu.Game.Overlays.News.Sidebar { @@ -20,15 +19,15 @@ namespace osu.Game.Overlays.News.Sidebar { private readonly Bindable metadata = new Bindable(); - private FillFlowContainer flow; + private Container gridPlaceholder; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, Bindable metadata) { this.metadata.BindTo(metadata); - Width = 160; AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; Masking = true; CornerRadius = 6; InternalChildren = new Drawable[] @@ -38,17 +37,11 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, - new Container + gridPlaceholder = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), - Child = flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(5) - } + Padding = new MarginPadding(5) } }; } @@ -65,7 +58,7 @@ namespace osu.Game.Overlays.News.Sidebar return; } - flow.Children = m.NewValue.Years.Select(y => new YearButton(y)).ToArray(); + gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years); Show(); }, true); } @@ -78,7 +71,8 @@ namespace osu.Game.Overlays.News.Sidebar public YearButton(int year) { - Size = new Vector2(33.75f, 15); + RelativeSizeAxes = Axes.X; + Height = 15; Child = text = new OsuSpriteText { Anchor = Anchor.Centre, @@ -96,5 +90,76 @@ namespace osu.Game.Overlays.News.Sidebar Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } + + private class YearsGridContainer : GridContainer + { + private const int column_count = 4; + private const float spacing = 5f; + + private readonly int rowCount; + private readonly int[] years; + + public YearsGridContainer(int[] years) + { + this.years = years; + rowCount = (int)Math.Ceiling((float)years.Length / column_count); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + RowDimensions = getRowDimensions(); + ColumnDimensions = getColumnDimensions(); + Content = createContent(); + } + + private Dimension[] getRowDimensions() + { + var rowDimensions = new Dimension[rowCount]; + for (int i = 0; i < rowCount; i++) + rowDimensions[i] = new Dimension(GridSizeMode.AutoSize); + + return rowDimensions; + } + + private Dimension[] getColumnDimensions() + { + var columnDimensions = new Dimension[column_count]; + for (int i = 0; i < column_count; i++) + columnDimensions[i] = new Dimension(GridSizeMode.Relative, size: 1f / column_count); + + return columnDimensions; + } + + private Drawable[][] createContent() + { + var buttons = new Drawable[rowCount][]; + + for (int i = 0; i < rowCount; i++) + { + buttons[i] = new Drawable[column_count]; + + for (int j = 0; j < column_count; j++) + { + var index = i * column_count + j; + buttons[i][j] = index >= years.Length + ? Empty() + : new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Top = i == 0 ? 0 : spacing / 2, + Bottom = i == rowCount - 1 ? 0 : spacing / 2, + Left = j == 0 ? 0 : spacing / 2, + Right = j == column_count - 1 ? 0 : spacing / 2 + }, + Child = new YearButton(years[index]) + }; + } + } + + return buttons; + } + } } } From 5692cecaa47e9e071fb6079d9f3847b8b1de0972 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 16:35:05 +0900 Subject: [PATCH 064/304] Initial implementation of DHO pooling --- .../Skinning/TestSceneColumnBackground.cs | 4 +- .../Skinning/TestSceneKeyArea.cs | 4 +- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 10 +--- .../Objects/Drawables/DrawableHoldNote.cs | 55 +++++++++---------- .../Objects/Drawables/DrawableHoldNoteHead.cs | 12 +++- .../Objects/Drawables/DrawableHoldNoteTail.cs | 17 ++++-- .../Objects/Drawables/DrawableHoldNoteTick.cs | 47 +++++++++++++--- .../Drawables/DrawableManiaHitObject.cs | 20 ++++++- .../Objects/Drawables/DrawableNote.cs | 26 +++++---- osu.Game.Rulesets.Mania/Objects/HeadNote.cs | 9 +++ osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 4 +- osu.Game.Rulesets.Mania/UI/Column.cs | 11 +++- .../UI/Components/ColumnHitObjectArea.cs | 2 +- .../UI/PoolableHitExplosion.cs | 2 +- 14 files changed, 146 insertions(+), 77 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Objects/HeadNote.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs index bde323f187..ca323b5911 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both } @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs index 7e80419944..c58c07c83b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 0), _ => new DefaultKeyArea()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both }, @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 1), _ => new DefaultKeyArea()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index f078345fc1..9aebf51576 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -9,12 +9,6 @@ namespace osu.Game.Rulesets.Mania { public class ManiaSkinComponent : GameplaySkinComponent { - /// - /// The intended index for this component. - /// May be null if the component does not exist in a . - /// - public readonly int? TargetColumn; - /// /// The intended for this component. /// May be null if the component is not a direct member of a . @@ -25,12 +19,10 @@ namespace osu.Game.Rulesets.Mania /// Creates a new . /// /// The component. - /// The intended index for this component. May be null if the component does not exist in a . /// The intended for this component. May be null if the component is not a direct member of a . - public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null) + public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition? stageDefinition = null) : base(component) { - TargetColumn = targetColumn; StageDefinition = stageDefinition; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 02829d87bd..e1cf1851df 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -29,21 +30,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public DrawableHoldNoteHead Head => headContainer.Child; public DrawableHoldNoteTail Tail => tailContainer.Child; - private readonly Container headContainer; - private readonly Container tailContainer; - private readonly Container tickContainer; + private Container headContainer; + private Container tailContainer; + private Container tickContainer; /// /// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed. /// - private readonly Container sizingContainer; + private Container sizingContainer; /// /// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of . /// - private readonly Container maskingContainer; + private Container maskingContainer; - private readonly SkinnableDrawable bodyPiece; + private SkinnableDrawable bodyPiece; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -60,11 +61,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private double? releaseTime; + public DrawableHoldNote() + : this(null) + { + } + public DrawableHoldNote(HoldNote hitObject) : base(hitObject) { - RelativeSizeAxes = Axes.X; + } + [BackgroundDependencyLoader] + private void load() + { Container maskedContents; AddRangeInternal(new Drawable[] @@ -86,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables headContainer = new Container { RelativeSizeAxes = Axes.Both } } }, - bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece { RelativeSizeAxes = Axes.Both, }) @@ -128,37 +137,23 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); - headContainer.Clear(); - tailContainer.Clear(); - tickContainer.Clear(); + headContainer.Clear(false); + tailContainer.Clear(false); + tickContainer.Clear(false); } protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { - case TailNote _: - return new DrawableHoldNoteTail(this) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AccentColour = { BindTarget = AccentColour } - }; + case TailNote tail: + return new DrawableHoldNoteTail(tail); - case Note _: - return new DrawableHoldNoteHead(this) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AccentColour = { BindTarget = AccentColour } - }; + case HeadNote head: + return new DrawableHoldNoteHead(head); case HoldNoteTick tick: - return new DrawableHoldNoteTick(tick) - { - HoldStartTime = () => HoldStartTime, - AccentColour = { BindTarget = AccentColour } - }; + return new DrawableHoldNoteTick(tick); } return base.CreateNestedHitObject(hitObject); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index 35ba2465fa..be600f0d47 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -12,11 +13,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead; - public DrawableHoldNoteHead(DrawableHoldNote holdNote) - : base(holdNote.HitObject.Head) + public DrawableHoldNoteHead() + : this(null) { } + public DrawableHoldNoteHead(HeadNote headNote) + : base(headNote) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + } + public void UpdateResult() => base.UpdateResult(true); protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index 3a00933e4d..18aa3f66d4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -20,12 +21,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail; - private readonly DrawableHoldNote holdNote; + protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject; - public DrawableHoldNoteTail(DrawableHoldNote holdNote) - : base(holdNote.HitObject.Tail) + public DrawableHoldNoteTail() + : this(null) { - this.holdNote = holdNote; + } + + public DrawableHoldNoteTail(TailNote tailNote) + : base(tailNote) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; } public void UpdateResult() => base.UpdateResult(true); @@ -54,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables ApplyResult(r => { // If the head wasn't hit or the hold note was broken, cap the max score to Meh. - if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HoldBrokenTime != null)) + if (result > HitResult.Meh && (!HoldNote.Head.IsHit || HoldNote.HoldBrokenTime != null)) result = HitResult.Meh; r.Type = result; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 98931dceed..6a57849fb4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; +using System.Diagnostics; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,20 +20,28 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// References the time at which the user started holding the hold note. /// - public Func HoldStartTime; + private Func holdStartTime; + + private Container glowContainer; + + public DrawableHoldNoteTick() + : this(null) + { + } public DrawableHoldNoteTick(HoldNoteTick hitObject) : base(hitObject) { - Container glowContainer; - Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; RelativeSizeAxes = Axes.X; - Size = new Vector2(1); + } - AddRangeInternal(new[] + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new[] { glowContainer = new CircularContainer { @@ -50,7 +59,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } } - }); + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); AccentColour.BindValueChanged(colour => { @@ -64,12 +78,29 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }, true); } + protected override void OnApply() + { + base.OnApply(); + + Debug.Assert(ParentHitObject != null); + + var holdNote = (DrawableHoldNote)ParentHitObject; + holdStartTime = () => holdNote.HoldStartTime; + } + + protected override void OnFree() + { + base.OnFree(); + + holdStartTime = null; + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (Time.Current < HitObject.StartTime) return; - var startTime = HoldStartTime?.Invoke(); + var startTime = holdStartTime?.Invoke(); if (startTime == null || startTime > HitObject.StartTime) ApplyResult(r => r.Type = r.Judgement.MinResult); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 003646d654..7323ae9df1 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected DrawableManiaHitObject(ManiaHitObject hitObject) : base(hitObject) { + RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader(true)] @@ -62,6 +63,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Direction.BindValueChanged(OnDirectionChanged, true); } + protected override void OnApply() + { + base.OnApply(); + + if (ParentHitObject != null) + AccentColour.BindTo(ParentHitObject.AccentColour); + } + + protected override void OnFree() + { + base.OnFree(); + + if (ParentHitObject != null) + AccentColour.UnbindFrom(ParentHitObject.AccentColour); + } + private double computedLifetimeStart; public override double LifetimeStart @@ -147,12 +164,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public abstract class DrawableManiaHitObject : DrawableManiaHitObject where TObject : ManiaHitObject { - public new readonly TObject HitObject; + public new TObject HitObject => (TObject)base.HitObject; protected DrawableManiaHitObject(TObject hitObject) : base(hitObject) { - HitObject = hitObject; } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 36565e14aa..16feec6b3a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -33,31 +33,35 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; - private readonly Drawable headPiece; + private Drawable headPiece; + + public DrawableNote() + : this(null) + { + } public DrawableNote(Note hitObject) : base(hitObject) { - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - - AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component, hitObject.Column), _ => new DefaultNotePiece()) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }); } [BackgroundDependencyLoader(true)] private void load(ManiaRulesetConfigManager rulesetConfig) { rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); + + InternalChild = headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece()) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }; } protected override void LoadComplete() { - HitObject.StartTimeBindable.BindValueChanged(_ => updateSnapColour()); - configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true); + configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour()); + StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true); } protected override void OnDirectionChanged(ValueChangedEvent e) @@ -102,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void updateSnapColour() { - if (beatmap == null) return; + if (beatmap == null || HitObject == null) return; int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime); diff --git a/osu.Game.Rulesets.Mania/Objects/HeadNote.cs b/osu.Game.Rulesets.Mania/Objects/HeadNote.cs new file mode 100644 index 0000000000..e69cc62aed --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/HeadNote.cs @@ -0,0 +1,9 @@ +// 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.Rulesets.Mania.Objects +{ + public class HeadNote : Note + { + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 6cc7ff92d3..43e876b7aa 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// The head note of the hold. /// - public Note Head { get; private set; } + public HeadNote Head { get; private set; } /// /// The tail note of the hold. @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Objects createTicks(cancellationToken); - AddNested(Head = new Note + AddNested(Head = new HeadNote { StartTime = StartTime, Column = Column, diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 0f02e2cd4b..8247a37881 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; namespace osu.Game.Rulesets.Mania.UI @@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y; Width = COLUMN_WIDTH; - Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground()) + Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both }; @@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea()) + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both }, @@ -83,6 +84,12 @@ namespace osu.Game.Rulesets.Mania.UI hitPolicy = new OrderedHitPolicy(HitObjectContainer); TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); + + RegisterPool(5, 20); + RegisterPool(5, 20); + RegisterPool(5, 20); + RegisterPool(5, 20); + RegisterPool(50, 200); } public ColumnType ColumnType { get; set; } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index b365ae45a9..f69d2aafdc 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.Both, Depth = 2, }, - hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget()) + hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget()) { RelativeSizeAxes = Axes.X, Depth = 1 diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs index 64b7d7d550..90d3c6c4c7 100644 --- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI [BackgroundDependencyLoader] private void load() { - InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), _ => new DefaultHitExplosion()) + InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => new DefaultHitExplosion()) { RelativeSizeAxes = Axes.Both }; From 4e7551d50ef672362d11a9241b277617f47dac73 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 16:40:46 +0900 Subject: [PATCH 065/304] Fix crashes --- .../Objects/Drawables/DrawableHoldNoteTick.cs | 25 ++++++++----------- .../Drawables/DrawableManiaHitObject.cs | 6 +++++ .../Objects/Drawables/DrawableNote.cs | 4 +-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 6a57849fb4..f040dad135 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -41,25 +41,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [BackgroundDependencyLoader] private void load() { - InternalChildren = new[] + AddInternal(glowContainer = new CircularContainer { - glowContainer = new CircularContainer + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new[] + new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true } } - }; + }); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 7323ae9df1..380ab35339 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -60,6 +60,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Action.BindTo(action); Direction.BindTo(scrollingInfo.Direction); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Direction.BindValueChanged(OnDirectionChanged, true); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 16feec6b3a..b711e67f25 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -51,11 +51,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); - InternalChild = headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece()) + AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece()) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y - }; + }); } protected override void LoadComplete() From 789025a7cea6f3621301f011dd182954f61ff806 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 16:56:07 +0900 Subject: [PATCH 066/304] Update playfield/stage/column implementations for pooling --- osu.Game.Rulesets.Mania/UI/Column.cs | 31 +++++++--------- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 5 +++ osu.Game.Rulesets.Mania/UI/Stage.cs | 37 ++++++++++++++++---- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 8247a37881..1ee3f8c668 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -92,6 +92,13 @@ namespace osu.Game.Rulesets.Mania.UI RegisterPool(50, 200); } + protected override void LoadComplete() + { + base.LoadComplete(); + + NewResult += OnNewResult; + } + public ColumnType ColumnType { get; set; } public bool IsSpecial => ColumnType == ColumnType.Special; @@ -105,28 +112,14 @@ namespace osu.Game.Rulesets.Mania.UI return dependencies; } - /// - /// Adds a DrawableHitObject to this Playfield. - /// - /// The DrawableHitObject to add. - public override void Add(DrawableHitObject hitObject) + protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) { - hitObject.AccentColour.Value = AccentColour; - hitObject.OnNewResult += OnNewResult; + base.OnNewDrawableHitObject(drawableHitObject); - DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject; + drawableHitObject.AccentColour.Value = AccentColour; + + DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject; maniaObject.CheckHittable = hitPolicy.IsHittable; - - base.Add(hitObject); - } - - public override bool Remove(DrawableHitObject h) - { - if (!base.Remove(h)) - return false; - - h.OnNewResult -= OnNewResult; - return true; } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 271e432e8d..8830c440c0 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -56,6 +57,10 @@ namespace osu.Game.Rulesets.Mania.UI } } + public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject); + + public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject); + public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h); public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index dc34bffab1..eee75f3200 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -132,10 +133,37 @@ namespace osu.Game.Rulesets.Mania.UI } } + protected override void LoadComplete() + { + base.LoadComplete(); + NewResult += OnNewResult; + } + + public override void Add(HitObject hitObject) + { + var maniaObject = (ManiaHitObject)hitObject; + int columnIndex = -1; + + maniaObject.ColumnBindable.BindValueChanged(_ => + { + if (columnIndex != -1) + Columns.ElementAt(columnIndex).Remove(hitObject); + + columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Add(hitObject); + }, true); + } + + public override bool Remove(HitObject hitObject) + { + var maniaObject = (ManiaHitObject)hitObject; + int columnIndex = maniaObject.Column - firstColumnIndex; + return Columns.ElementAt(columnIndex).Remove(hitObject); + } + public override void Add(DrawableHitObject h) { var maniaObject = (ManiaHitObject)h.HitObject; - int columnIndex = -1; maniaObject.ColumnBindable.BindValueChanged(_ => @@ -146,18 +174,13 @@ namespace osu.Game.Rulesets.Mania.UI columnIndex = maniaObject.Column - firstColumnIndex; Columns.ElementAt(columnIndex).Add(h); }, true); - - h.OnNewResult += OnNewResult; } public override bool Remove(DrawableHitObject h) { var maniaObject = (ManiaHitObject)h.HitObject; int columnIndex = maniaObject.Column - firstColumnIndex; - Columns.ElementAt(columnIndex).Remove(h); - - h.OnNewResult -= OnNewResult; - return true; + return Columns.ElementAt(columnIndex).Remove(h); } public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); From 7913189aa911e814025e5083eddc7de5d74c5c51 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 16:56:23 +0900 Subject: [PATCH 067/304] Turn on pooling --- .../UI/DrawableManiaRuleset.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 4ee060e91e..e497646a13 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -18,7 +18,6 @@ using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -134,20 +133,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) - { - switch (h) - { - case HoldNote holdNote: - return new DrawableHoldNote(holdNote); - - case Note note: - return new DrawableNote(note); - - default: - return null; - } - } + public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) => null; protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); From 1af3bbf400d7267feac4627ef5d8e1ab2b87b855 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 17:06:44 +0900 Subject: [PATCH 068/304] Fix base.OnLoadComplete() not being called --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index b711e67f25..33d872dfb6 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void LoadComplete() { + base.LoadComplete(); + configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour()); StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true); } From f992b59b4f9b3ef5b8460704f3f878e818fe7367 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 17:07:42 +0900 Subject: [PATCH 069/304] Fix DrawableHoldNote retaining hit states through applications --- .../Objects/Drawables/DrawableHoldNote.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index e1cf1851df..d1310d42eb 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -114,6 +115,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } + protected override void OnApply() + { + base.OnApply(); + + sizingContainer.Size = Vector2.One; + HoldStartTime = null; + HoldBrokenTime = null; + releaseTime = null; + } + protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); From 0fa3027ab9eea096fc792a6a4bd9ff6a4944e130 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 17:36:26 +0900 Subject: [PATCH 070/304] Increase pool sizes a bit --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 1ee3f8c668..57d3f54716 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -85,11 +85,11 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); - RegisterPool(5, 20); - RegisterPool(5, 20); - RegisterPool(5, 20); - RegisterPool(5, 20); - RegisterPool(50, 200); + RegisterPool(10, 50); + RegisterPool(10, 50); + RegisterPool(10, 50); + RegisterPool(10, 50); + RegisterPool(50, 250); } protected override void LoadComplete() From 315a2b8314a044689eb607dfdced7bc24fc1f4c9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 12 May 2021 20:50:20 +0300 Subject: [PATCH 071/304] Refactor MonthDropdown to decouple autosized logic --- .../Overlays/News/Sidebar/MonthDropdown.cs | 122 ++++++++++-------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 91412d9527..d6d7527a6c 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -20,85 +20,37 @@ namespace osu.Game.Overlays.News.Sidebar { public class MonthDropdown : CompositeDrawable { - private const int header_height = 15; private const int animation_duration = 250; public readonly BindableBool IsOpen = new BindableBool(); - private readonly FillFlowContainer postsFlow; - public MonthDropdown(int month, int year, IEnumerable posts) { Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; Masking = true; InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), Children = new Drawable[] { new DropdownHeader(month, year) { IsOpen = { BindTarget = IsOpen } }, - postsFlow = new FillFlowContainer + new PostsContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + IsOpen = { BindTarget = IsOpen }, Children = posts.Select(p => new PostButton(p)).ToArray() } } }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - IsOpen.BindValueChanged(open => - { - ClearTransforms(); - - if (open.NewValue) - { - AutoSizeAxes = Axes.Y; - postsFlow.FadeIn(animation_duration, Easing.OutQuint); - } - else - { - AutoSizeAxes = Axes.None; - this.ResizeHeightTo(header_height, animation_duration, Easing.OutQuint); - - postsFlow.FadeOut(animation_duration, Easing.OutQuint); - } - }, true); - - // First state change should be instant. - FinishTransforms(true); - } - - private bool shouldUpdateAutosize = true; - - // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. - protected override void UpdateAfterAutoSize() - { - base.UpdateAfterAutoSize(); - - if (shouldUpdateAutosize) - { - AutoSizeDuration = animation_duration; - AutoSizeEasing = Easing.OutQuint; - - shouldUpdateAutosize = false; - } - } - private class DropdownHeader : OsuClickableContainer { public readonly BindableBool IsOpen = new BindableBool(); @@ -110,7 +62,7 @@ namespace osu.Game.Overlays.News.Sidebar var date = new DateTime(year, month, 1); RelativeSizeAxes = Axes.X; - Height = header_height; + Height = 15; Action = IsOpen.Toggle; Children = new Drawable[] { @@ -152,7 +104,6 @@ namespace osu.Game.Overlays.News.Sidebar { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) { RelativeSizeAxes = Axes.X, @@ -169,5 +120,70 @@ namespace osu.Game.Overlays.News.Sidebar Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } + + private class PostsContainer : Container + { + public readonly BindableBool IsOpen = new BindableBool(); + + protected override Container Content => content; + + private readonly FillFlowContainer content; + + public PostsContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = content = new FillFlowContainer + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsOpen.BindValueChanged(open => + { + ClearTransforms(); + + if (open.NewValue) + { + AutoSizeAxes = Axes.Y; + content.FadeIn(animation_duration, Easing.OutQuint); + } + else + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); + + content.FadeOut(animation_duration, Easing.OutQuint); + } + }, true); + + // First state change should be instant. + FinishTransforms(true); + } + + private bool shouldUpdateAutosize = true; + + // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. + protected override void UpdateAfterAutoSize() + { + base.UpdateAfterAutoSize(); + + if (shouldUpdateAutosize) + { + AutoSizeDuration = animation_duration; + AutoSizeEasing = Easing.OutQuint; + + shouldUpdateAutosize = false; + } + } + } } } From 9a061ad80b38eea677eed6bb235951c8f035bb6b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 5 May 2021 20:27:28 +0200 Subject: [PATCH 072/304] Extract directory selection logic of migration screen to DirectorySelectScreen. --- .../Maintenance/DirectorySelectScreen.cs | 115 ++++++++++++++++++ .../Maintenance/MigrationSelectScreen.cs | 105 +++------------- 2 files changed, 130 insertions(+), 90 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs new file mode 100644 index 0000000000..278c1ab20d --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using System.IO; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens; +using osuTK; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Screens; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public abstract class DirectorySelectScreen : OsuScreen + { + private TriangleButton selectionButton; + + protected DirectorySelector DirectorySelector { get; private set; } + + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + + protected abstract OsuSpriteText CreateHeader(); + + /// + /// Called upon selection of a directory by the user. + /// + /// The selected directory + protected abstract void OnSelection(DirectoryInfo directory); + + protected virtual bool IsValidDirectory(DirectoryInfo info) => info != null; + + public override bool AllowExternalScreenChange => false; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new Container + { + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f, 0.8f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDark + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Relative, 0.8f), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + CreateHeader().With(header => + { + header.Origin = Anchor.Centre; + header.Anchor = Anchor.Centre; + }) + }, + new Drawable[] + { + DirectorySelector = new DirectorySelector + { + RelativeSizeAxes = Axes.Both, + } + }, + new Drawable[] + { + selectionButton = new TriangleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, + Text = "Select directory", + Action = () => OnSelection(DirectorySelector.CurrentPath.Value) + }, + } + } + } + } + }; + } + + protected override void LoadComplete() + { + DirectorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); + base.LoadComplete(); + } + + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + + this.FadeOut(250); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index ad540e3691..1f14d0991d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -4,109 +4,28 @@ using System; using System.IO; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Screens; -using osuTK; namespace osu.Game.Overlays.Settings.Sections.Maintenance { - public class MigrationSelectScreen : OsuScreen + public class MigrationSelectScreen : DirectorySelectScreen { - private DirectorySelector directorySelector; + [Resolved] + private Storage storage { get; set; } - public override bool AllowExternalScreenChange => false; - - public override bool DisallowExternalBeatmapRulesetChanges => true; - - public override bool HideOverlaysOnEnter => true; - - [BackgroundDependencyLoader(true)] - private void load(OsuGame game, Storage storage, OsuColour colours) + protected override OsuSpriteText CreateHeader() => new OsuSpriteText { - game?.Toolbar.Hide(); + Text = "Please select a new location", + Font = OsuFont.Default.With(size: 40) + }; - // begin selection in the parent directory of the current storage location - var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; - - InternalChild = new Container - { - Masking = true, - CornerRadius = 10, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0.5f, 0.8f), - Children = new Drawable[] - { - new Box - { - Colour = colours.GreySeafoamDark, - RelativeSizeAxes = Axes.Both, - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Relative, 0.8f), - new Dimension(), - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Please select a new location", - Font = OsuFont.Default.With(size: 40) - }, - }, - new Drawable[] - { - directorySelector = new DirectorySelector(initialPath) - { - RelativeSizeAxes = Axes.Both, - } - }, - new Drawable[] - { - new TriangleButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 300, - Text = "Begin folder migration", - Action = start - }, - } - } - } - } - }; - } - - public override void OnSuspending(IScreen next) + protected override void OnSelection(DirectoryInfo directory) { - base.OnSuspending(next); - - this.FadeOut(250); - } - - private void start() - { - var target = directorySelector.CurrentPath.Value; + var target = directory; try { @@ -123,6 +42,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance BeginMigration(target); } + protected override void LoadComplete() + { + DirectorySelector.CurrentPath.Value = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; + base.LoadComplete(); + } + protected virtual void BeginMigration(DirectoryInfo target) => this.Push(new MigrationRunScreen(target)); } } From 4bb0e6b7d53bca6188470600b5ca3d6d1b007e65 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 5 May 2021 22:13:25 +0200 Subject: [PATCH 073/304] Create InitialPath property. --- .../Sections/Maintenance/DirectorySelectScreen.cs | 5 +++++ .../Sections/Maintenance/MigrationSelectScreen.cs | 8 ++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 278c1ab20d..a6ae3d6132 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -34,6 +34,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected virtual bool IsValidDirectory(DirectoryInfo info) => info != null; + protected virtual DirectoryInfo InitialPath => null; + public override bool AllowExternalScreenChange => false; public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -101,6 +103,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override void LoadComplete() { + if (InitialPath != null) + DirectorySelector.CurrentPath.Value = InitialPath; + DirectorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); base.LoadComplete(); } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 1f14d0991d..cfbb7d6593 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -17,6 +17,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [Resolved] private Storage storage { get; set; } + protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; + protected override OsuSpriteText CreateHeader() => new OsuSpriteText { Text = "Please select a new location", @@ -42,12 +44,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance BeginMigration(target); } - protected override void LoadComplete() - { - DirectorySelector.CurrentPath.Value = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; - base.LoadComplete(); - } - protected virtual void BeginMigration(DirectoryInfo target) => this.Push(new MigrationRunScreen(target)); } } From d3cc418961462f9961ba8551e18671c7046c92ed Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 6 May 2021 11:12:19 +0200 Subject: [PATCH 074/304] Privatize DirectorySelector. --- .../Sections/Maintenance/DirectorySelectScreen.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index a6ae3d6132..c08323e67c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private TriangleButton selectionButton; - protected DirectorySelector DirectorySelector { get; private set; } + private DirectorySelector directorySelector; protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }, new Drawable[] { - DirectorySelector = new DirectorySelector + directorySelector = new DirectorySelector { RelativeSizeAxes = Axes.Both, } @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Origin = Anchor.Centre, Width = 300, Text = "Select directory", - Action = () => OnSelection(DirectorySelector.CurrentPath.Value) + Action = () => OnSelection(directorySelector.CurrentPath.Value) }, } } @@ -104,9 +104,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override void LoadComplete() { if (InitialPath != null) - DirectorySelector.CurrentPath.Value = InitialPath; + directorySelector.CurrentPath.Value = InitialPath; - DirectorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); + directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); base.LoadComplete(); } From eee3cd7c5770c1e4e42383980ad9b8d65b6381e6 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 11:14:05 +0200 Subject: [PATCH 075/304] Disallow selecting storage root as a valid directory. --- .../Settings/Sections/Maintenance/DirectorySelectScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index c08323e67c..582d14fbb6 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance /// The selected directory protected abstract void OnSelection(DirectoryInfo directory); - protected virtual bool IsValidDirectory(DirectoryInfo info) => info != null; + protected virtual bool IsValidDirectory(DirectoryInfo info) => true; protected virtual DirectoryInfo InitialPath => null; @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (InitialPath != null) directorySelector.CurrentPath.Value = InitialPath; - directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); + directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null ? IsValidDirectory(e.NewValue) : false, true); base.LoadComplete(); } From ffb6135a1bd0dd531d86ff238c62ed0dfecda5b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 19:53:32 +0900 Subject: [PATCH 076/304] Rework hitobject blueprints to take in hitobject models --- .../TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../Editor/TestSceneNoteSelectionBlueprint.cs | 2 +- .../HoldNoteNoteSelectionBlueprint.cs | 5 +++-- .../Blueprints/HoldNoteSelectionBlueprint.cs | 9 ++++---- .../Blueprints/ManiaSelectionBlueprint.cs | 9 ++++---- .../Edit/Blueprints/NoteSelectionBlueprint.cs | 6 ++--- .../Edit/ManiaBlueprintContainer.cs | 11 +++++----- .../Edit/ManiaSelectionHandler.cs | 5 ++--- .../TestSceneHitCircleSelectionBlueprint.cs | 6 ++--- .../TestSceneSliderControlPointPiece.cs | 8 +++---- .../TestSceneSliderSelectionBlueprint.cs | 8 +++---- .../TestSceneSpinnerSelectionBlueprint.cs | 2 +- .../HitCircles/HitCircleSelectionBlueprint.cs | 4 ++-- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 10 ++++----- .../Sliders/SliderCircleSelectionBlueprint.cs | 3 +-- .../Sliders/SliderSelectionBlueprint.cs | 16 +++++--------- .../Spinners/SpinnerSelectionBlueprint.cs | 3 +-- .../Edit/OsuBlueprintContainer.cs | 13 +++++------ .../Blueprints/TaikoSelectionBlueprint.cs | 6 ++--- .../Edit/TaikoBlueprintContainer.cs | 3 +-- ...rint.cs => HitObjectSelectionBlueprint.cs} | 22 ++++++++++++++----- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 12 +++++----- .../Components/ComposeBlueprintContainer.cs | 5 ++--- .../Compose/Components/MoveSelectionEvent.cs | 2 +- .../Visual/SelectionBlueprintTestScene.cs | 4 +++- 25 files changed, 90 insertions(+), 86 deletions(-) rename osu.Game/Rulesets/Edit/{OverlaySelectionBlueprint.cs => HitObjectSelectionBlueprint.cs} (68%) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 24f4c6858e..5e99264d7d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor } }; - AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject)); + AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableObject); } protected override void Update() diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index 0e47a12a8e..9c3ad0b4ff 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor Child = drawableObject = new DrawableNote(note) }; - AddBlueprint(new NoteSelectionBlueprint(drawableObject)); + AddBlueprint(new NoteSelectionBlueprint(note), drawableObject); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs index 4e73883de0..14c8d488a9 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs @@ -3,17 +3,18 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint + public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint { protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; private readonly HoldNotePosition position; - public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position) + public HoldNoteNoteSelectionBlueprint(HoldNote holdNote, HoldNotePosition position) : base(holdNote) { this.position = position; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 1737c4d2e5..c7ee6097c6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -8,13 +8,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint + public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private OsuColour colours { get; set; } - public HoldNoteSelectionBlueprint(DrawableHoldNote hold) + public HoldNoteSelectionBlueprint(HoldNote hold) : base(hold) { } @@ -40,8 +41,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChildren = new Drawable[] { - new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start), - new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End), + new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), + new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 384f49d9b2..e744bd3c83 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -4,22 +4,23 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint + public abstract class ManiaSelectionBlueprint : HitObjectSelectionBlueprint + where T : ManiaHitObject { public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; [Resolved] private IScrollingInfo scrollingInfo { get; set; } - protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) - : base(drawableObject) + protected ManiaSelectionBlueprint(T hitObject) + : base(hitObject) { RelativeSizeAxes = Axes.None; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 2bff33c4cf..e2b6ee0048 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -3,13 +3,13 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; -using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class NoteSelectionBlueprint : ManiaSelectionBlueprint + public class NoteSelectionBlueprint : ManiaSelectionBlueprint { - public NoteSelectionBlueprint(DrawableNote note) + public NoteSelectionBlueprint(Note note) : base(note) { AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X }); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index c4429176d1..c5a109a6d1 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -3,9 +3,8 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; -using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Mania.Edit @@ -17,18 +16,18 @@ namespace osu.Game.Rulesets.Mania.Edit { } - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) { switch (hitObject) { - case DrawableNote note: + case Note note: return new NoteSelectionBlueprint(note); - case DrawableHoldNote holdNote: + case HoldNote holdNote: return new HoldNoteSelectionBlueprint(holdNote); } - return base.CreateBlueprintFor(hitObject); + return base.CreateHitObjectBlueprintFor(hitObject); } protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 7042110423..8a1446db32 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -23,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Edit public override bool HandleMovement(MoveSelectionEvent moveEvent) { - var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; - int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; + var maniaBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint; + int lastColumn = maniaBlueprint.HitObject.Column; performColumnMovement(lastColumn, moveEvent); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs index 66cd405195..315493318d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableHitCircle(hitCircle)); - AddBlueprint(blueprint = new TestBlueprint(drawableObject)); + AddBlueprint(blueprint = new TestBlueprint(hitCircle), drawableObject); }); [Test] @@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestBlueprint(DrawableHitCircle drawableCircle) - : base(drawableCircle) + public TestBlueprint(HitCircle circle) + : base(circle) { } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index d7dfc3bd42..fe0f2f8a87 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableSlider(slider)); - AddBlueprint(new TestSliderBlueprint(drawableObject)); + AddBlueprint(new TestSliderBlueprint(slider), drawableObject); }); [Test] @@ -154,19 +154,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; - public TestSliderBlueprint(DrawableSlider slider) + public TestSliderBlueprint(Slider slider) : base(slider) { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); } private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) + public TestSliderCircleBlueprint(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index f6e1be693b..721bb1985d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableSlider(slider)); - AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject)); + AddBlueprint(blueprint = new TestSliderBlueprint(slider), drawableObject); }); [Test] @@ -199,19 +199,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; - public TestSliderBlueprint(DrawableSlider slider) + public TestSliderBlueprint(Slider slider) : base(slider) { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); } private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) + public TestSliderCircleBlueprint(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs index 4248f68a60..5007841805 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Child = drawableSpinner = new DrawableSpinner(spinner) }); - AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }); + AddBlueprint(new SpinnerSelectionBlueprint(spinner) { Size = new Vector2(0.5f) }, drawableSpinner); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index abbb54e3c1..b21a3e038e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles protected readonly HitCirclePiece CirclePiece; - public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle) - : base(drawableCircle) + public HitCircleSelectionBlueprint(HitCircle circle) + : base(circle) { InternalChild = CirclePiece = new HitCirclePiece(); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index 299f8fc43a..994c5cebeb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -2,20 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Edit.Blueprints { - public abstract class OsuSelectionBlueprint : OverlaySelectionBlueprint + public abstract class OsuSelectionBlueprint : HitObjectSelectionBlueprint where T : OsuHitObject { - protected T HitObject => (T)DrawableObject.HitObject; + protected new DrawableOsuHitObject DrawableObject => (DrawableOsuHitObject)base.DrawableObject; protected override bool AlwaysShowWhenSelected => true; - protected OsuSelectionBlueprint(DrawableHitObject drawableObject) - : base(drawableObject) + protected OsuSelectionBlueprint(T hitObject) + : base(hitObject) { } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index dec9cd8622..ff01c7a442 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { @@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly SliderPosition position; - public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) + public SliderCircleSelectionBlueprint(Slider slider, SliderPosition position) : base(slider) { this.position = position; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 88fcb1e715..939f4cdc3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -33,8 +32,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } - private readonly DrawableSlider slider; - [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -52,10 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly BindableList controlPoints = new BindableList(); private readonly IBindable pathVersion = new Bindable(); - public SliderSelectionBlueprint(DrawableSlider slider) + public SliderSelectionBlueprint(Slider slider) : base(slider) { - this.slider = slider; } [BackgroundDependencyLoader] @@ -64,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders InternalChildren = new Drawable[] { BodyPiece = new SliderBodyPiece(), - HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), - TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), + HeadBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.Start), + TailBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.End), }; } @@ -103,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnSelected() { - AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(slider.HitObject, true) + AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true) { RemoveControlPointsRequested = removeControlPoints }); @@ -215,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted - if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength) + if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength) { placementHandler?.Delete(HitObject); return; @@ -245,6 +241,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; - protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); + protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs index f05d4f8435..ee573d1a01 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners { private readonly SpinnerPiece piece; - public SpinnerSelectionBlueprint(DrawableSpinner spinner) + public SpinnerSelectionBlueprint(Spinner spinner) : base(spinner) { InternalChild = piece = new SpinnerPiece(); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index abac5eb56e..dc8c3d6107 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -3,11 +3,10 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; -using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit @@ -21,21 +20,21 @@ namespace osu.Game.Rulesets.Osu.Edit protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) { switch (hitObject) { - case DrawableHitCircle circle: + case HitCircle circle: return new HitCircleSelectionBlueprint(circle); - case DrawableSlider slider: + case Slider slider: return new SliderSelectionBlueprint(slider); - case DrawableSpinner spinner: + case Spinner spinner: return new SpinnerSelectionBlueprint(spinner); } - return base.CreateBlueprintFor(hitObject); + return base.CreateHitObjectBlueprintFor(hitObject); } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs index 62f69122cc..01b90c4bca 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs @@ -3,14 +3,14 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { - public class TaikoSelectionBlueprint : OverlaySelectionBlueprint + public class TaikoSelectionBlueprint : HitObjectSelectionBlueprint { - public TaikoSelectionBlueprint(DrawableHitObject hitObject) + public TaikoSelectionBlueprint(HitObject hitObject) : base(hitObject) { RelativeSizeAxes = Axes.None; diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index b1b08a9461..a465638779 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osu.Game.Screens.Edit.Compose.Components; @@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Edit protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => new TaikoSelectionBlueprint(hitObject); } } diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs similarity index 68% rename from osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs rename to osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 7911cf874b..56434b1d82 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -9,12 +9,12 @@ using osuTK; namespace osu.Game.Rulesets.Edit { - public abstract class OverlaySelectionBlueprint : SelectionBlueprint + public abstract class HitObjectSelectionBlueprint : SelectionBlueprint { /// - /// The which this applies to. + /// The which this applies to. /// - public readonly DrawableHitObject DrawableObject; + public DrawableHitObject DrawableObject { get; internal set; } /// /// Whether the blueprint should be shown even when the is not alive. @@ -23,10 +23,9 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); - protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) - : base(drawableObject.HitObject) + protected HitObjectSelectionBlueprint(HitObject hitObject) + : base(hitObject) { - DrawableObject = drawableObject; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); @@ -35,4 +34,15 @@ namespace osu.Game.Rulesets.Edit public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; } + + public abstract class HitObjectSelectionBlueprint : HitObjectSelectionBlueprint + where T : HitObject + { + public T HitObject => (T)Item; + + protected HitObjectSelectionBlueprint(T item) + : base(item) + { + } + } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 55703a2cd3..f59ad8bfa2 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -105,34 +105,34 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; /// - /// Deselects this , causing it to become invisible. + /// Deselects this , causing it to become invisible. /// public void Deselect() => State = SelectionState.NotSelected; /// - /// Toggles the selection state of this . + /// Toggles the selection state of this . /// public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected; public bool IsSelected => State == SelectionState.Selected; /// - /// The s to be displayed in the context menu for this . + /// The s to be displayed in the context menu for this . /// public virtual MenuItem[] ContextMenuItems => Array.Empty(); /// - /// The screen-space point that causes this to be selected via a drag. + /// The screen-space point that causes this to be selected via a drag. /// public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; /// - /// The screen-space quad that outlines this for selections. + /// The screen-space quad that outlines this for selections. /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 6c174e563e..95f4069edb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; @@ -246,10 +245,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateBlueprintFor(drawable); + return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); } - public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; + public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; protected override void OnBlueprintAdded(HitObject item) { diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 4d4f4b76c6..e32dcc81ee 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index 1176361679..dc12a4999d 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Tests.Visual { @@ -22,10 +23,11 @@ namespace osu.Game.Tests.Visual }); } - protected void AddBlueprint(OverlaySelectionBlueprint blueprint) + protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject drawableObject) { Add(blueprint.With(d => { + d.DrawableObject = drawableObject; d.Depth = float.MinValue; d.Select(); })); From 1ae57a6105b124bbfb39a8a2f9e980014385f6f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 20:11:04 +0900 Subject: [PATCH 077/304] Fix hold note input test Not sure why this was checking visibility. If it needs to be tested, it does not belong in an "Input" test. --- osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 668487f673..aa4eb77280 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -5,13 +5,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; @@ -414,13 +412,6 @@ namespace osu.Game.Rulesets.Mania.Tests AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); - AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head); - AddAssert("head is visible", - () => currentPlayer.ChildrenOfType() - .Single(note => note.HitObject == beatmap.HitObjects[0]) - .Head - .Alpha == 1); - AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } From 98e77a30d39910c35593ac89ea7369787b6b3fb6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 20:13:50 +0900 Subject: [PATCH 078/304] Move column changing logic to ManiaSelectionHandler --- .../Edit/ManiaSelectionHandler.cs | 5 ++- osu.Game.Rulesets.Mania/UI/Stage.cs | 44 ++----------------- 2 files changed, 7 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 7042110423..83fafbc9d1 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -59,8 +59,9 @@ namespace osu.Game.Rulesets.Mania.Edit EditorBeatmap.PerformOnSelection(h => { - if (h is ManiaHitObject maniaObj) - maniaObj.Column += columnDelta; + maniaPlayfield.Remove(h); + ((ManiaHitObject)h).Column += columnDelta; + maniaPlayfield.Add(h); }); } } diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index eee75f3200..8c703e7a8a 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -139,49 +139,13 @@ namespace osu.Game.Rulesets.Mania.UI NewResult += OnNewResult; } - public override void Add(HitObject hitObject) - { - var maniaObject = (ManiaHitObject)hitObject; - int columnIndex = -1; + public override void Add(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Add(hitObject); - maniaObject.ColumnBindable.BindValueChanged(_ => - { - if (columnIndex != -1) - Columns.ElementAt(columnIndex).Remove(hitObject); + public override bool Remove(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Remove(hitObject); - columnIndex = maniaObject.Column - firstColumnIndex; - Columns.ElementAt(columnIndex).Add(hitObject); - }, true); - } + public override void Add(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Add(h); - public override bool Remove(HitObject hitObject) - { - var maniaObject = (ManiaHitObject)hitObject; - int columnIndex = maniaObject.Column - firstColumnIndex; - return Columns.ElementAt(columnIndex).Remove(hitObject); - } - - public override void Add(DrawableHitObject h) - { - var maniaObject = (ManiaHitObject)h.HitObject; - int columnIndex = -1; - - maniaObject.ColumnBindable.BindValueChanged(_ => - { - if (columnIndex != -1) - Columns.ElementAt(columnIndex).Remove(h); - - columnIndex = maniaObject.Column - firstColumnIndex; - Columns.ElementAt(columnIndex).Add(h); - }, true); - } - - public override bool Remove(DrawableHitObject h) - { - var maniaObject = (ManiaHitObject)h.HitObject; - int columnIndex = maniaObject.Column - firstColumnIndex; - return Columns.ElementAt(columnIndex).Remove(h); - } + public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h); public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); From 86042e176387aaf259d1ac4a378058af74f4c6cb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 20:56:38 +0900 Subject: [PATCH 079/304] Implement HitObjectContainerEventQueue --- .../Compose/HitObjectContainerEventQueue.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs diff --git a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs new file mode 100644 index 0000000000..b22d0a75e9 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Screens.Edit.Compose +{ + /// + /// A queue which processes events from the many s in a nested hierarchy. + /// + internal class HitObjectContainerEventQueue : Component + { + /// + /// Invoked when a becomes used by a . + /// + /// + /// If the ruleset uses pooled objects, this represents the time when the s become alive. + /// + public event Action HitObjectUsageBegan; + + /// + /// Invoked when a becomes unused by a . + /// + /// + /// If the ruleset uses pooled objects, this represents the time when the s become dead. + /// + public event Action HitObjectUsageFinished; + + /// + /// Invoked when a has been transferred to another . + /// + public event Action HitObjectUsageTransferred; + + private readonly Playfield playfield; + + /// + /// Creates a new . + /// + /// The most top-level . + public HitObjectContainerEventQueue(Playfield playfield) + { + this.playfield = playfield; + + bindPlayfieldRecursive(playfield); + } + + private void bindPlayfieldRecursive(Playfield p) + { + p.HitObjectContainer.HitObjectUsageBegan += onHitObjectUsageBegan; + p.HitObjectContainer.HitObjectUsageFinished += onHitObjectUsageFinished; + + foreach (var nested in p.NestedPlayfields) + bindPlayfieldRecursive(nested); + } + + private readonly Dictionary pendingUsagesBegan = new Dictionary(); + private readonly Dictionary pendingUsagesFinished = new Dictionary(); + + private void onHitObjectUsageBegan(HitObject hitObject) => pendingUsagesBegan[hitObject] = pendingUsagesBegan.GetValueOrDefault(hitObject, 0) + 1; + + private void onHitObjectUsageFinished(HitObject hitObject) => pendingUsagesFinished[hitObject] = pendingUsagesFinished.GetValueOrDefault(hitObject, 0) + 1; + + protected override void Update() + { + base.Update(); + + foreach (var (hitObject, countBegan) in pendingUsagesBegan) + { + if (pendingUsagesFinished.TryGetValue(hitObject, out int countFinished)) + { + Debug.Assert(countFinished > 0); + + if (countBegan > countFinished) + { + // The hitobject is still in use, but transferred to a different HOC. + HitObjectUsageTransferred?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); + pendingUsagesFinished.Remove(hitObject); + } + } + else + { + // This is a new usage of the hitobject. + HitObjectUsageBegan?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); + } + } + + // Go through any remaining pending finished usages. + foreach (var (hitObject, _) in pendingUsagesFinished) + HitObjectUsageFinished?.Invoke(hitObject); + + pendingUsagesBegan.Clear(); + pendingUsagesFinished.Clear(); + } + } +} From aaf31af32668d4fd9560c7645c6139a3e021b9c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 21:16:19 +0900 Subject: [PATCH 080/304] Add blueprint transferral --- .../Compose/Components/BlueprintContainer.cs | 7 +++++ .../Components/ComposeBlueprintContainer.cs | 9 +++++++ .../Components/EditorBlueprintContainer.cs | 18 +++++++------ .../Compose/HitObjectContainerEventQueue.cs | 27 ++++++++++--------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 361e98e0dd..951cc99c85 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -276,6 +276,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { } + /// + /// Retrieves an item's blueprint. + /// + /// The item to retrieve the blueprint of. + /// The blueprint. + protected SelectionBlueprint GetBlueprintFor(T item) => blueprintMap[item]; + #endregion #region Selection diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 95f4069edb..e231f7f648 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; @@ -73,6 +74,14 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) + { + base.TransferBlueprintFor(hitObject, drawableObject); + + var blueprint = (HitObjectSelectionBlueprint)GetBlueprintFor(hitObject); + blueprint.DrawableObject = drawableObject; + } + protected override bool OnKeyDown(KeyDownEvent e) { if (e.ControlPressed) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index db322faf65..063023c849 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Screens.Edit.Compose.Components { @@ -65,8 +66,11 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var obj in Composer.HitObjects) AddBlueprintFor(obj.HitObject); - Composer.Playfield.HitObjectUsageBegan += AddBlueprintFor; - Composer.Playfield.HitObjectUsageFinished += RemoveBlueprintFor; + var eventQueue = new HitObjectContainerEventQueue(Composer.Playfield); + eventQueue.HitObjectUsageBegan += AddBlueprintFor; + eventQueue.HitObjectUsageFinished += RemoveBlueprintFor; + eventQueue.HitObjectUsageTransferred += TransferBlueprintFor; + AddInternal(eventQueue); } } @@ -100,6 +104,10 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(item); } + protected virtual void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) + { + } + protected override void DragOperationCompleted() { base.DragOperationCompleted(); @@ -152,12 +160,6 @@ namespace osu.Game.Screens.Edit.Compose.Components Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; } - - if (Composer != null) - { - Composer.Playfield.HitObjectUsageBegan -= AddBlueprintFor; - Composer.Playfield.HitObjectUsageFinished -= RemoveBlueprintFor; - } } } } diff --git a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs index b22d0a75e9..26f5a28113 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -23,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose /// /// If the ruleset uses pooled objects, this represents the time when the s become alive. /// - public event Action HitObjectUsageBegan; + public event Action HitObjectUsageBegan; /// /// Invoked when a becomes unused by a . @@ -44,20 +45,12 @@ namespace osu.Game.Screens.Edit.Compose /// Creates a new . /// /// The most top-level . - public HitObjectContainerEventQueue(Playfield playfield) + public HitObjectContainerEventQueue([NotNull] Playfield playfield) { this.playfield = playfield; - bindPlayfieldRecursive(playfield); - } - - private void bindPlayfieldRecursive(Playfield p) - { - p.HitObjectContainer.HitObjectUsageBegan += onHitObjectUsageBegan; - p.HitObjectContainer.HitObjectUsageFinished += onHitObjectUsageFinished; - - foreach (var nested in p.NestedPlayfields) - bindPlayfieldRecursive(nested); + playfield.HitObjectUsageBegan += onHitObjectUsageBegan; + playfield.HitObjectUsageFinished += onHitObjectUsageFinished; } private readonly Dictionary pendingUsagesBegan = new Dictionary(); @@ -87,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose else { // This is a new usage of the hitobject. - HitObjectUsageBegan?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); + HitObjectUsageBegan?.Invoke(hitObject); } } @@ -98,5 +91,13 @@ namespace osu.Game.Screens.Edit.Compose pendingUsagesBegan.Clear(); pendingUsagesFinished.Clear(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + playfield.HitObjectUsageBegan -= onHitObjectUsageBegan; + playfield.HitObjectUsageFinished -= onHitObjectUsageFinished; + } } } From 2307889bf81762d647718a17d0d4e326782dd4b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 21:41:06 +0900 Subject: [PATCH 081/304] Fix incorrect cast --- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 8a1446db32..a0a43ed6ca 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Edit public override bool HandleMovement(MoveSelectionEvent moveEvent) { - var maniaBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint; - int lastColumn = maniaBlueprint.HitObject.Column; + var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint; + int lastColumn = ((ManiaHitObject)hitObjectBlueprint.Item).Column; performColumnMovement(lastColumn, moveEvent); From 362a09ca73d491890698e1f84c6de2239ee6b129 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 21:41:18 +0900 Subject: [PATCH 082/304] Fix up + reduce complexity of HOCEventQueue --- .../Compose/HitObjectContainerEventQueue.cs | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs index 26f5a28113..7c21573b18 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Graphics; @@ -53,43 +52,59 @@ namespace osu.Game.Screens.Edit.Compose playfield.HitObjectUsageFinished += onHitObjectUsageFinished; } - private readonly Dictionary pendingUsagesBegan = new Dictionary(); - private readonly Dictionary pendingUsagesFinished = new Dictionary(); + private readonly Dictionary pendingEvents = new Dictionary(); - private void onHitObjectUsageBegan(HitObject hitObject) => pendingUsagesBegan[hitObject] = pendingUsagesBegan.GetValueOrDefault(hitObject, 0) + 1; + private void onHitObjectUsageBegan(HitObject hitObject) => updateEvent(hitObject, EventType.Began); - private void onHitObjectUsageFinished(HitObject hitObject) => pendingUsagesFinished[hitObject] = pendingUsagesFinished.GetValueOrDefault(hitObject, 0) + 1; + private void onHitObjectUsageFinished(HitObject hitObject) => updateEvent(hitObject, EventType.Finished); + + private void updateEvent(HitObject hitObject, EventType newEvent) + { + if (!pendingEvents.TryGetValue(hitObject, out EventType existingEvent)) + { + pendingEvents[hitObject] = newEvent; + return; + } + + switch (existingEvent, newEvent) + { + case (EventType.Transferred, EventType.Finished): + pendingEvents[hitObject] = EventType.Finished; + break; + + case (EventType.Began, EventType.Finished): + case (EventType.Finished, EventType.Began): + pendingEvents[hitObject] = EventType.Transferred; + break; + + default: + throw new ArgumentOutOfRangeException($"Unexpected event update ({existingEvent} => {newEvent})."); + } + } protected override void Update() { base.Update(); - foreach (var (hitObject, countBegan) in pendingUsagesBegan) + foreach (var (hitObject, e) in pendingEvents) { - if (pendingUsagesFinished.TryGetValue(hitObject, out int countFinished)) + switch (e) { - Debug.Assert(countFinished > 0); + case EventType.Began: + HitObjectUsageBegan?.Invoke(hitObject); + break; - if (countBegan > countFinished) - { - // The hitobject is still in use, but transferred to a different HOC. + case EventType.Transferred: HitObjectUsageTransferred?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); - pendingUsagesFinished.Remove(hitObject); - } - } - else - { - // This is a new usage of the hitobject. - HitObjectUsageBegan?.Invoke(hitObject); + break; + + case EventType.Finished: + HitObjectUsageFinished?.Invoke(hitObject); + break; } } - // Go through any remaining pending finished usages. - foreach (var (hitObject, _) in pendingUsagesFinished) - HitObjectUsageFinished?.Invoke(hitObject); - - pendingUsagesBegan.Clear(); - pendingUsagesFinished.Clear(); + pendingEvents.Clear(); } protected override void Dispose(bool isDisposing) @@ -99,5 +114,12 @@ namespace osu.Game.Screens.Edit.Compose playfield.HitObjectUsageBegan -= onHitObjectUsageBegan; playfield.HitObjectUsageFinished -= onHitObjectUsageFinished; } + + private enum EventType + { + Began, + Finished, + Transferred + } } } From 38c0ba2d1034b756f5d6e8b1f007afee44c9e648 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 13 May 2021 16:16:19 +0300 Subject: [PATCH 083/304] Implement current year highlight in YearsPanel --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 39 +++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 2a4e2024d2..2d405e832b 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; using System; +using osuTK.Graphics; namespace osu.Game.Overlays.News.Sidebar { @@ -58,7 +59,7 @@ namespace osu.Game.Overlays.News.Sidebar return; } - gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years); + gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); Show(); }, true); } @@ -68,16 +69,19 @@ namespace osu.Game.Overlays.News.Sidebar protected override IEnumerable EffectTargets => new[] { text }; private readonly OsuSpriteText text; + private readonly bool isCurrent; - public YearButton(int year) + public YearButton(int year, bool isCurrent) { + this.isCurrent = isCurrent; + RelativeSizeAxes = Axes.X; Height = 15; Child = text = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: isCurrent ? FontWeight.SemiBold : FontWeight.Medium), Text = year.ToString() }; } @@ -85,8 +89,8 @@ namespace osu.Game.Overlays.News.Sidebar [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Light1; + IdleColour = isCurrent ? Color4.White : colourProvider.Light2; + HoverColour = isCurrent ? Color4.White : colourProvider.Light1; Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } @@ -97,18 +101,16 @@ namespace osu.Game.Overlays.News.Sidebar private const float spacing = 5f; private readonly int rowCount; - private readonly int[] years; - public YearsGridContainer(int[] years) + public YearsGridContainer(int[] years, int currentYear) { - this.years = years; rowCount = (int)Math.Ceiling((float)years.Length / column_count); RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; RowDimensions = getRowDimensions(); ColumnDimensions = getColumnDimensions(); - Content = createContent(); + Content = createContent(years, currentYear); } private Dimension[] getRowDimensions() @@ -129,7 +131,7 @@ namespace osu.Game.Overlays.News.Sidebar return columnDimensions; } - private Drawable[][] createContent() + private Drawable[][] createContent(int[] years, int currentYear) { var buttons = new Drawable[rowCount][]; @@ -140,9 +142,17 @@ namespace osu.Game.Overlays.News.Sidebar for (int j = 0; j < column_count; j++) { var index = i * column_count + j; - buttons[i][j] = index >= years.Length - ? Empty() - : new Container + + if (index >= years.Length) + { + buttons[i][j] = Empty(); + } + else + { + var year = years[index]; + var isCurrent = year == currentYear; + + buttons[i][j] = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -153,8 +163,9 @@ namespace osu.Game.Overlays.News.Sidebar Left = j == 0 ? 0 : spacing / 2, Right = j == column_count - 1 ? 0 : spacing / 2 }, - Child = new YearButton(years[index]) + Child = new YearButton(year, isCurrent) }; + } } } From 09a5b9c872eaee74470f9e3cbe9c3e789715e882 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 16:28:03 +0200 Subject: [PATCH 084/304] Add XMLDoc to protected members. --- .../Sections/Maintenance/DirectorySelectScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 582d14fbb6..c676e1391d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -32,8 +32,16 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance /// The selected directory protected abstract void OnSelection(DirectoryInfo directory); + /// + /// Whether the current directory is considered to be valid and can be selected. + /// + /// The current directory. + /// Whether the selected directory is considered valid. protected virtual bool IsValidDirectory(DirectoryInfo info) => true; + /// + /// The path at which to start selection from. + /// protected virtual DirectoryInfo InitialPath => null; public override bool AllowExternalScreenChange => false; From 0caba57945ba161274132260772580b8f747e9d2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 19:28:23 +0200 Subject: [PATCH 085/304] Make screen properties local to MigrationSelectScreen. --- .../Settings/Sections/Maintenance/DirectorySelectScreen.cs | 6 ------ .../Settings/Sections/Maintenance/MigrationSelectScreen.cs | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index c676e1391d..6e6df14a92 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -22,8 +22,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private DirectorySelector directorySelector; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; - protected abstract OsuSpriteText CreateHeader(); /// @@ -44,10 +42,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance /// protected virtual DirectoryInfo InitialPath => null; - public override bool AllowExternalScreenChange => false; - - public override bool DisallowExternalBeatmapRulesetChanges => true; - [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index cfbb7d6593..6334bffe94 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; + public override bool AllowExternalScreenChange => false; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override bool HideOverlaysOnEnter => true; + protected override OsuSpriteText CreateHeader() => new OsuSpriteText { Text = "Please select a new location", From 6f248db519146eace1face0eb55c96d4c6b3784c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 19:31:10 +0200 Subject: [PATCH 086/304] Merge conditional expression. --- .../Settings/Sections/Maintenance/DirectorySelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 6e6df14a92..c2366ca209 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -108,7 +108,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (InitialPath != null) directorySelector.CurrentPath.Value = InitialPath; - directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null ? IsValidDirectory(e.NewValue) : false, true); + directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null && IsValidDirectory(e.NewValue), true); base.LoadComplete(); } From dc576c19b47fa64e74bfb9fd440dfccf345bd195 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 15:10:02 +0900 Subject: [PATCH 087/304] Fix a potential nullref when starting `Player` with autoplay enabled and beatmap fails to load --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ac0c921ccf..15983d1d62 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -154,6 +154,10 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); + // BDL load may have aborted early. + if (DrawableRuleset == null) + return; + // replays should never be recorded or played back when autoplay is enabled if (!Mods.Value.Any(m => m is ModAutoplay)) PrepareReplay(); From f5dd18f266a11ecc941b2ebd65c6ea40adb70cab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 16:53:51 +0900 Subject: [PATCH 088/304] Use existing `LoadedBeatmapSuccessfully` bool instead Co-authored-by: Salman Ahmed --- osu.Game/Screens/Play/Player.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 15983d1d62..06dfd2fdb7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -154,8 +154,7 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - // BDL load may have aborted early. - if (DrawableRuleset == null) + if (!LoadedBeatmapSuccessfully) return; // replays should never be recorded or played back when autoplay is enabled From 67dfeeb1b77eaa009aff89aa666fdf3ad12ec365 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:23:56 +0800 Subject: [PATCH 089/304] Cleanup code in ModHidden --- .../Mods/CatchModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 14 ++++---- osu.Game/Rulesets/Mods/ModHidden.cs | 35 ------------------- 3 files changed, 8 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index bba42dea97..ba3e8f9a8f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Catch.Mods var offset = hitObject.TimePreempt * fade_out_offset_multiplier; var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier; - using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset, true)) + using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset)) drawable.FadeOut(duration); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 45f314af7b..b0f3f49939 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -65,13 +65,13 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawableObject) { case DrawableSliderTail _: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) drawableObject.FadeOut(fadeOut.duration); break; case DrawableSliderRepeat sliderRepeat: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) // only apply to circle piece – reverse arrow is not affected by hidden. sliderRepeat.CirclePiece.FadeOut(fadeOut.duration); @@ -88,22 +88,22 @@ namespace osu.Game.Rulesets.Osu.Mods else { // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true)) + using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) circle.ApproachCircle.Hide(); } - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) fadeTarget.FadeOut(fadeOut.duration); break; case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (slider.BeginAbsoluteSequence(fadeOut.startTime)) slider.Body.FadeOut(fadeOut.duration, Easing.Out); break; case DrawableSliderTick sliderTick: - using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime)) sliderTick.FadeOut(fadeOut.duration); break; @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (spinner.BeginAbsoluteSequence(fadeOut.startTime)) spinner.FadeOut(fadeOut.duration); break; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index df421adbe5..03932baca9 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.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 System; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; @@ -18,14 +17,6 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; - /// - /// Check whether the provided hitobject should be considered the "first" hideable object. - /// Can be used to skip spinners, for instance. - /// - /// The hitobject to check. - [Obsolete("Use IsFirstAdjustableObject() instead.")] // Can be removed 20210506 - protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true; - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ @@ -49,36 +40,10 @@ namespace osu.Game.Rulesets.Mods protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { -#pragma warning disable 618 - ApplyFirstObjectIncreaseVisibilityState(hitObject, state); -#pragma warning restore 618 } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { -#pragma warning disable 618 - ApplyHiddenState(hitObject, state); -#pragma warning restore 618 - } - - /// - /// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting. - /// - /// The hit object to apply the state change to. - /// The state of the hit object. - [Obsolete("Use ApplyIncreasedVisibilityState() instead.")] // Can be removed 20210506 - protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } - - /// - /// Apply a hidden state to the provided object. - /// - /// The hit object to apply the state change to. - /// The state of the hit object. - [Obsolete("Use ApplyNormalVisibilityState() instead.")] // Can be removed 20210506 - protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) - { } } } From 393ac4fdd1d6ab2ea08b0658f9d907696f1937d1 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:30:58 +0800 Subject: [PATCH 090/304] Destruct declaration --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index b0f3f49939..4f1e91974a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -60,20 +60,20 @@ namespace osu.Game.Rulesets.Osu.Mods OsuHitObject hitObject = drawableOsuObject.HitObject; - (double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject); + (double startTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject); switch (drawableObject) { case DrawableSliderTail _: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) - drawableObject.FadeOut(fadeOut.duration); + using (drawableObject.BeginAbsoluteSequence(startTime)) + drawableObject.FadeOut(fadeDuration); break; case DrawableSliderRepeat sliderRepeat: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) + using (drawableObject.BeginAbsoluteSequence(startTime)) // only apply to circle piece – reverse arrow is not affected by hidden. - sliderRepeat.CirclePiece.FadeOut(fadeOut.duration); + sliderRepeat.CirclePiece.FadeOut(fadeDuration); break; @@ -92,19 +92,19 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) - fadeTarget.FadeOut(fadeOut.duration); + using (drawableObject.BeginAbsoluteSequence(startTime)) + fadeTarget.FadeOut(fadeDuration); break; case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(fadeOut.startTime)) - slider.Body.FadeOut(fadeOut.duration, Easing.Out); + using (slider.BeginAbsoluteSequence(startTime)) + slider.Body.FadeOut(fadeDuration, Easing.Out); break; case DrawableSliderTick sliderTick: - using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime)) - sliderTick.FadeOut(fadeOut.duration); + using (sliderTick.BeginAbsoluteSequence(startTime)) + sliderTick.FadeOut(fadeDuration); break; @@ -112,14 +112,14 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(fadeOut.startTime)) - spinner.FadeOut(fadeOut.duration); + using (spinner.BeginAbsoluteSequence(startTime)) + spinner.FadeOut(fadeDuration); break; } } - private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject) + private (double startTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject) { switch (drawableObject) { @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Mods return getParameters(drawableObject.HitObject); } - static (double startTime, double duration) getParameters(OsuHitObject hitObject) + static (double startTime, double fadeDuration) getParameters(OsuHitObject hitObject) { var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; From a86a4bab9137ef6781404dc116101662c4c6d858 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:53:59 +0800 Subject: [PATCH 091/304] Remove empty override --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 -- osu.Game/Rulesets/Mods/ModHidden.cs | 9 --------- 2 files changed, 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 4f1e91974a..c2ddeb5fcc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -43,13 +43,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { - base.ApplyIncreasedVisibilityState(hitObject, state); applyState(hitObject, true); } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - base.ApplyNormalVisibilityState(hitObject, state); applyState(hitObject, false); } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 03932baca9..238612b3d2 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -37,13 +36,5 @@ namespace osu.Game.Rulesets.Mods return rank; } } - - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } - - protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } } } From a4c1b9a1a7195dca0f4b5f5f078774f306c0c916 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:56:13 +0800 Subject: [PATCH 092/304] Rename startTime to fadeStartTime --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index c2ddeb5fcc..4168293749 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -58,18 +58,18 @@ namespace osu.Game.Rulesets.Osu.Mods OsuHitObject hitObject = drawableOsuObject.HitObject; - (double startTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject); + (double fadeStartTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject); switch (drawableObject) { case DrawableSliderTail _: - using (drawableObject.BeginAbsoluteSequence(startTime)) + using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) drawableObject.FadeOut(fadeDuration); break; case DrawableSliderRepeat sliderRepeat: - using (drawableObject.BeginAbsoluteSequence(startTime)) + using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) // only apply to circle piece – reverse arrow is not affected by hidden. sliderRepeat.CirclePiece.FadeOut(fadeDuration); @@ -90,18 +90,18 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - using (drawableObject.BeginAbsoluteSequence(startTime)) + using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) fadeTarget.FadeOut(fadeDuration); break; case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(startTime)) + using (slider.BeginAbsoluteSequence(fadeStartTime)) slider.Body.FadeOut(fadeDuration, Easing.Out); break; case DrawableSliderTick sliderTick: - using (sliderTick.BeginAbsoluteSequence(startTime)) + using (sliderTick.BeginAbsoluteSequence(fadeStartTime)) sliderTick.FadeOut(fadeDuration); break; @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(startTime)) + using (spinner.BeginAbsoluteSequence(fadeStartTime)) spinner.FadeOut(fadeDuration); break; From 63ac430386a631ea683496527aa7f8cb47b5f065 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 15 May 2021 11:26:16 +0800 Subject: [PATCH 093/304] Rename startTime in parameters --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 4168293749..2752feb0a1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private (double startTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject) + private (double fadeStartTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject) { switch (drawableObject) { @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Mods return getParameters(drawableObject.HitObject); } - static (double startTime, double fadeDuration) getParameters(OsuHitObject hitObject) + static (double fadeStartTime, double fadeDuration) getParameters(OsuHitObject hitObject) { var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; From 6e5c4ed7c6c3e4890c1480d7ccac1d52b131c8cf Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 15 May 2021 11:37:04 +0800 Subject: [PATCH 094/304] Revert "Remove empty override" This reverts commit a86a4bab9137ef6781404dc116101662c4c6d858. --- osu.Game/Rulesets/Mods/ModHidden.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 238612b3d2..03932baca9 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -36,5 +37,13 @@ namespace osu.Game.Rulesets.Mods return rank; } } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } } } From 166974506ea25739eb3ad48c7e9206370a684b4b Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 15 May 2021 11:51:39 +0800 Subject: [PATCH 095/304] Duplicate implementions --- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 6 ++++-- osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs | 9 +++++++++ osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 9 +++++++++ osu.Game/Rulesets/Mods/ModHidden.cs | 9 --------- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index ba3e8f9a8f..7bad4c79cb 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -28,10 +28,12 @@ namespace osu.Game.Rulesets.Catch.Mods catchPlayfield.CatcherArea.MovableCatcher.CatchFruitOnPlate = false; } + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - base.ApplyNormalVisibilityState(hitObject, state); - if (!(hitObject is DrawableCatchHitObject catchDrawable)) return; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 87501d07a5..3c24e91d54 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods @@ -39,5 +40,13 @@ namespace osu.Game.Rulesets.Mania.Mods })); } } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index a6f902208c..7739ecaf5b 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Mods { @@ -10,5 +11,13 @@ namespace osu.Game.Rulesets.Taiko.Mods public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; public override bool HasImplementation => false; + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } } } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 03932baca9..238612b3d2 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -37,13 +36,5 @@ namespace osu.Game.Rulesets.Mods return rank; } } - - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } - - protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } } } From e479db91864dd9a097aa90407940fd9b3639004b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 15 May 2021 19:14:02 +0300 Subject: [PATCH 096/304] Clear transforms in PostsContainer for all children --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index d6d7527a6c..85a06c8227 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -149,7 +149,7 @@ namespace osu.Game.Overlays.News.Sidebar IsOpen.BindValueChanged(open => { - ClearTransforms(); + ClearTransforms(true); if (open.NewValue) { From 3e1b1c6c3ebb539c8bc9d8cbb3b5c709aab2b0fc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 15 May 2021 19:14:58 +0300 Subject: [PATCH 097/304] Improve statement readability --- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 3851dea83a..837c661cbd 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.News.Sidebar var allPosts = metadata.NewValue.NewsPosts; - if (!allPosts?.Any() ?? true) + if (allPosts?.Any() != true) return; var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); From 3519398a224ba44f2f2e91ba6dd3aa921741a6a9 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 11:16:12 +0800 Subject: [PATCH 098/304] Added "flip" mod for taiko --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 32 ++++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs new file mode 100644 index 0000000000..1f2f2c4784 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModFlip : Mod, IApplicableToBeatmap + { + public override string Name => "Flip"; + public override string Acronym => "FP"; + public override string Description => @"Dons become kats, kats become dons"; + public override ModType Type => ModType.Conversion; + public override double ScoreMultiplier => 1; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var taikoBeatmap = (TaikoBeatmap)beatmap; + + foreach (var obj in taikoBeatmap.HitObjects) + { + if (obj is Hit hit) + hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index f4e158ec32..26dc1bd416 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { - new TaikoModRandom(), + new MultiMod(new TaikoModFlip(), new TaikoModRandom()), new TaikoModDifficultyAdjust(), new TaikoModClassic(), }; From da13be6dd0d9ec1dca90b46649e9a3e6a3f6c31d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 11:28:11 +0800 Subject: [PATCH 099/304] Trimmed trailing white space --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index 1f2f2c4784..e2de6ccc3d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Mods foreach (var obj in taikoBeatmap.HitObjects) { if (obj is Hit hit) - hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre; + hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre; } } } From 3d83741a23c6c0be53c71000148a3d7531e580c2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 12:03:03 +0800 Subject: [PATCH 100/304] Separate Flip and Random --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 3 +++ osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 3 +++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index e2de6ccc3d..22e627ef06 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -17,6 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override string Description => @"Dons become kats, kats become dons"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index 1cf19ac18e..e203bbc431 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 26dc1bd416..52088b7ac4 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -133,7 +133,8 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { - new MultiMod(new TaikoModFlip(), new TaikoModRandom()), + new TaikoModFlip(), + new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), }; From cbc2a38b590429ccf07d4aea7e8d4083899aba01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 May 2021 13:21:06 +0900 Subject: [PATCH 101/304] Move new mod to end to avoid reordering --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 52088b7ac4..9232ea63ee 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -133,10 +133,10 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { - new TaikoModFlip(), new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), + new TaikoModFlip(), }; case ModType.Automation: From 422a3b76b64532f69b1432ff01d2c0d39aa68f12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 May 2021 13:21:19 +0900 Subject: [PATCH 102/304] Remove unused using statements --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index 22e627ef06..bfab98fbf8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -3,9 +3,7 @@ using System; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; From 5972e43bc2a038df5e8c2c8f97e17ae28fe79399 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 12:51:40 +0800 Subject: [PATCH 103/304] Renamed TaikoModFlip to TaikoModInvert --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 12 +----------- .../Mods/{TaikoModFlip.cs => TaikoModInvert.cs} | 6 +----- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Mods/ModInvert.cs | 16 ++++++++++++++++ 5 files changed, 20 insertions(+), 18 deletions(-) rename osu.Game.Rulesets.Taiko/Mods/{TaikoModFlip.cs => TaikoModInvert.cs} (77%) create mode 100644 osu.Game/Rulesets/Mods/ModInvert.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 1ea45c295c..0366c0f374 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; @@ -13,19 +12,10 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion + public class ManiaModInvert : ModInvert, IApplicableAfterBeatmapConversion { - public override string Name => "Invert"; - - public override string Acronym => "IN"; - public override double ScoreMultiplier => 1; - public override string Description => "Hold the keys. To the beat."; - public override IconUsage? Icon => FontAwesome.Solid.YinYang; - - public override ModType Type => ModType.Conversion; - public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs similarity index 77% rename from osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs rename to osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs index bfab98fbf8..a6ccebaa61 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs @@ -10,13 +10,9 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModFlip : Mod, IApplicableToBeatmap + public class TaikoModInvert : ModInvert, IApplicableToBeatmap { - public override string Name => "Flip"; - public override string Acronym => "FP"; public override string Description => @"Dons become kats, kats become dons"; - public override ModType Type => ModType.Conversion; - public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index e203bbc431..a5ab176176 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModInvert)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9232ea63ee..8a716bf56e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), - new TaikoModFlip(), + new TaikoModInvert(), }; case ModType.Automation: diff --git a/osu.Game/Rulesets/Mods/ModInvert.cs b/osu.Game/Rulesets/Mods/ModInvert.cs new file mode 100644 index 0000000000..6c211d2173 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModInvert.cs @@ -0,0 +1,16 @@ +// 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; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModInvert : Mod + { + public override string Name => "Invert"; + public override string Acronym => "IN"; + public override ModType Type => ModType.Conversion; + public override IconUsage? Icon => FontAwesome.Solid.YinYang; + public override double ScoreMultiplier => 1; + } +} From c4ae70a82720a04a82502d8bacf158040ac412d9 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 May 2021 10:59:56 +0800 Subject: [PATCH 104/304] Revert "Renamed TaikoModFlip to TaikoModInvert" This reverts commit 5972e43bc2a038df5e8c2c8f97e17ae28fe79399. --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 12 +++++++++++- .../Mods/{TaikoModInvert.cs => TaikoModFlip.cs} | 6 +++++- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Mods/ModInvert.cs | 16 ---------------- 5 files changed, 18 insertions(+), 20 deletions(-) rename osu.Game.Rulesets.Taiko/Mods/{TaikoModInvert.cs => TaikoModFlip.cs} (77%) delete mode 100644 osu.Game/Rulesets/Mods/ModInvert.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 0366c0f374..1ea45c295c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; @@ -12,10 +13,19 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModInvert : ModInvert, IApplicableAfterBeatmapConversion + public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion { + public override string Name => "Invert"; + + public override string Acronym => "IN"; + public override double ScoreMultiplier => 1; + public override string Description => "Hold the keys. To the beat."; + public override IconUsage? Icon => FontAwesome.Solid.YinYang; + + public override ModType Type => ModType.Conversion; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs similarity index 77% rename from osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs rename to osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index a6ccebaa61..bfab98fbf8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -10,9 +10,13 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModInvert : ModInvert, IApplicableToBeatmap + public class TaikoModFlip : Mod, IApplicableToBeatmap { + public override string Name => "Flip"; + public override string Acronym => "FP"; public override string Description => @"Dons become kats, kats become dons"; + public override ModType Type => ModType.Conversion; + public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index a5ab176176..e203bbc431 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModInvert)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 8a716bf56e..9232ea63ee 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), - new TaikoModInvert(), + new TaikoModFlip(), }; case ModType.Automation: diff --git a/osu.Game/Rulesets/Mods/ModInvert.cs b/osu.Game/Rulesets/Mods/ModInvert.cs deleted file mode 100644 index 6c211d2173..0000000000 --- a/osu.Game/Rulesets/Mods/ModInvert.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Sprites; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModInvert : Mod - { - public override string Name => "Invert"; - public override string Acronym => "IN"; - public override ModType Type => ModType.Conversion; - public override IconUsage? Icon => FontAwesome.Solid.YinYang; - public override double ScoreMultiplier => 1; - } -} From f34637ea9c06a7e3a00b45f3fc762a025548dfe5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 May 2021 11:04:01 +0800 Subject: [PATCH 105/304] Renamed TaikoModFlip to TaikoModSwap --- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 +- .../Mods/{TaikoModFlip.cs => TaikoModSwap.cs} | 6 +++--- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Rulesets.Taiko/Mods/{TaikoModFlip.cs => TaikoModSwap.cs} (86%) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index e203bbc431..a22f189d5e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModSwap)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs similarity index 86% rename from osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs rename to osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs index bfab98fbf8..3cb337c41d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs @@ -10,10 +10,10 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModFlip : Mod, IApplicableToBeatmap + public class TaikoModSwap : Mod, IApplicableToBeatmap { - public override string Name => "Flip"; - public override string Acronym => "FP"; + public override string Name => "Swap"; + public override string Acronym => "SW"; public override string Description => @"Dons become kats, kats become dons"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9232ea63ee..5854d4770c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), - new TaikoModFlip(), + new TaikoModSwap(), }; case ModType.Automation: From 50e2b5a32761768102975a76511d099bc51180d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:00:36 +0900 Subject: [PATCH 106/304] SideBar -> Sidebar --- osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs | 6 +++--- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index b5405a979e..e376d9b1ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -13,15 +13,15 @@ using osu.Game.Overlays.News.Sidebar; namespace osu.Game.Tests.Visual.Online { - public class TestSceneNewsSideBar : OsuTestScene + public class TestSceneNewsSidebar : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private NewsSideBar sidebar; + private NewsSidebar sidebar; [SetUp] - public void SetUp() => Schedule(() => Child = sidebar = new NewsSideBar()); + public void SetUp() => Schedule(() => Child = sidebar = new NewsSidebar()); [Test] public void TestYearsPanelVisibility() diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 837c661cbd..bad334cb2f 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -12,7 +12,7 @@ using System.Linq; namespace osu.Game.Overlays.News.Sidebar { - public class NewsSideBar : CompositeDrawable + public class NewsSidebar : CompositeDrawable { [Cached] public readonly Bindable Metadata = new Bindable(); From 22561cda1956e51f3747952f19fb1f59c9ebc9db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:02:21 +0900 Subject: [PATCH 107/304] MonthDropdown -> MonthSection --- osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs | 2 +- .../News/Sidebar/{MonthDropdown.cs => MonthSection.cs} | 4 ++-- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Overlays/News/Sidebar/{MonthDropdown.cs => MonthSection.cs} (97%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index e376d9b1ed..5e8cd397bc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online public void TestMetadataWithNoPosts() { AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); } private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs similarity index 97% rename from osu.Game/Overlays/News/Sidebar/MonthDropdown.cs rename to osu.Game/Overlays/News/Sidebar/MonthSection.cs index 85a06c8227..80c408bda5 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -18,13 +18,13 @@ using System.Diagnostics; namespace osu.Game.Overlays.News.Sidebar { - public class MonthDropdown : CompositeDrawable + public class MonthSection : CompositeDrawable { private const int animation_duration = 250; public readonly BindableBool IsOpen = new BindableBool(); - public MonthDropdown(int month, int year, IEnumerable posts) + public MonthSection(int month, int year, IEnumerable posts) { Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index bad334cb2f..b8d283b7e2 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.News.Sidebar [Cached] public readonly Bindable Metadata = new Bindable(); - private FillFlowContainer monthsFlow; + private FillFlowContainer monthsFlow; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.News.Sidebar Children = new Drawable[] { new YearsPanel(), - monthsFlow = new FillFlowContainer + monthsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.News.Sidebar var month = sortedKeys[i]; var posts = lookup[month]; - monthsFlow.Add(new MonthDropdown(month, year, posts) + monthsFlow.Add(new MonthSection(month, year, posts) { IsOpen = { Value = i == 0 } }); From 032f60819d0836fdd24a6fe715b08a3835e454c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:11:46 +0900 Subject: [PATCH 108/304] Rename content container --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 2d405e832b..932494f740 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.News.Sidebar { private readonly Bindable metadata = new Bindable(); - private Container gridPlaceholder; + private Container gridContent; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, Bindable metadata) @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, - gridPlaceholder = new Container + gridContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.News.Sidebar return; } - gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); + gridContent.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); Show(); }, true); } @@ -77,6 +77,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; Height = 15; + Child = text = new OsuSpriteText { Anchor = Anchor.Centre, From ae1e62288d57d1477155f439f4d2b094edd68447 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:16:50 +0900 Subject: [PATCH 109/304] Reorder tests to not have the first test show nothing --- .../Visual/Online/TestSceneNewsSideBar.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 5e8cd397bc..706de2b310 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -23,6 +23,13 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void SetUp() => Schedule(() => Child = sidebar = new NewsSidebar()); + [Test] + public void TestMetadataWithNoPosts() + { + AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + } + [Test] public void TestYearsPanelVisibility() { @@ -31,13 +38,6 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } - [Test] - public void TestMetadataWithNoPosts() - { - AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); - } - private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); private static readonly APINewsSidebar metadata = new APINewsSidebar From abeeda5d04ddb2922629fba6fea2812ca5335c27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:24:43 +0900 Subject: [PATCH 110/304] Rewrite `YearsPanel` to not be insanely long --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 49 ++++---------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 932494f740..4573ae530f 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -1,17 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using System.Collections.Generic; -using osu.Game.Graphics; -using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; -using System; using osuTK.Graphics; namespace osu.Game.Overlays.News.Sidebar @@ -77,6 +77,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; Height = 15; + Padding = new MarginPadding { Vertical = 2.5f }; Child = text = new OsuSpriteText { @@ -99,39 +100,19 @@ namespace osu.Game.Overlays.News.Sidebar private class YearsGridContainer : GridContainer { private const int column_count = 4; - private const float spacing = 5f; private readonly int rowCount; public YearsGridContainer(int[] years, int currentYear) { - rowCount = (int)Math.Ceiling((float)years.Length / column_count); + rowCount = (years.Length + column_count - 1) / column_count; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - RowDimensions = getRowDimensions(); - ColumnDimensions = getColumnDimensions(); + RowDimensions = Enumerable.Range(0, rowCount).Select(_ => new Dimension(GridSizeMode.AutoSize)).ToArray(); Content = createContent(years, currentYear); } - private Dimension[] getRowDimensions() - { - var rowDimensions = new Dimension[rowCount]; - for (int i = 0; i < rowCount; i++) - rowDimensions[i] = new Dimension(GridSizeMode.AutoSize); - - return rowDimensions; - } - - private Dimension[] getColumnDimensions() - { - var columnDimensions = new Dimension[column_count]; - for (int i = 0; i < column_count; i++) - columnDimensions[i] = new Dimension(GridSizeMode.Relative, size: 1f / column_count); - - return columnDimensions; - } - private Drawable[][] createContent(int[] years, int currentYear) { var buttons = new Drawable[rowCount][]; @@ -153,19 +134,7 @@ namespace osu.Game.Overlays.News.Sidebar var year = years[index]; var isCurrent = year == currentYear; - buttons[i][j] = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Top = i == 0 ? 0 : spacing / 2, - Bottom = i == rowCount - 1 ? 0 : spacing / 2, - Left = j == 0 ? 0 : spacing / 2, - Right = j == column_count - 1 ? 0 : spacing / 2 - }, - Child = new YearButton(year, isCurrent) - }; + buttons[i][j] = new YearButton(year, isCurrent); } } } From baa4089364f46eb59c1209dd94d2f2a78a2e718a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:40:42 +0900 Subject: [PATCH 111/304] Expose method to adjust header text, not whole drawable --- .../Maintenance/DirectorySelectScreen.cs | 16 +++++++++++----- .../Maintenance/MigrationSelectScreen.cs | 9 ++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index c2366ca209..e7c69e89fe 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -10,6 +10,7 @@ using osu.Game.Screens; using osuTK; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Framework.Screens; @@ -22,7 +23,10 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private DirectorySelector directorySelector; - protected abstract OsuSpriteText CreateHeader(); + /// + /// Text to display in the header to inform the user of what they are selecting. + /// + public abstract LocalisableString HeaderText { get; } /// /// Called upon selection of a directory by the user. @@ -73,11 +77,13 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { new Drawable[] { - CreateHeader().With(header => + new OsuSpriteText { - header.Origin = Anchor.Centre; - header.Anchor = Anchor.Centre; - }) + Text = HeaderText, + Font = OsuFont.Default.With(size: 40), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } }, new Drawable[] { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 6334bffe94..1a60ab0638 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -4,11 +4,10 @@ using System; using System.IO; using osu.Framework.Allocation; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -25,11 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public override bool HideOverlaysOnEnter => true; - protected override OsuSpriteText CreateHeader() => new OsuSpriteText - { - Text = "Please select a new location", - Font = OsuFont.Default.With(size: 40) - }; + public override LocalisableString HeaderText => "Please select a new location"; protected override void OnSelection(DirectoryInfo directory) { From e754d2e59028940ff1a2e8b06cf66e88d1187299 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 10:54:45 +0300 Subject: [PATCH 112/304] Simplify YearButton --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 4573ae530f..2528d51331 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.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 System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -66,9 +65,6 @@ namespace osu.Game.Overlays.News.Sidebar private class YearButton : OsuHoverContainer { - protected override IEnumerable EffectTargets => new[] { text }; - - private readonly OsuSpriteText text; private readonly bool isCurrent; public YearButton(int year, bool isCurrent) @@ -79,7 +75,7 @@ namespace osu.Game.Overlays.News.Sidebar Height = 15; Padding = new MarginPadding { Vertical = 2.5f }; - Child = text = new OsuSpriteText + Child = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, From e3b8d8ee1875d357db0198894fb8c475969f090e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 17 May 2021 16:58:54 +0900 Subject: [PATCH 113/304] Add support for overlay-coloured links --- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index d27a3fbffe..e7f47833a2 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Online.Chat @@ -20,7 +21,10 @@ namespace osu.Game.Online.Chat /// /// Each word part of a chat link (split for word-wrap support). /// - public List Parts; + public readonly List Parts; + + [Resolved(CanBeNull = true)] + private OverlayColourProvider overlayColourProvider { get; set; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); @@ -34,7 +38,7 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.Blue; + IdleColour = overlayColourProvider?.Light2 ?? colours.Blue; } protected override IEnumerable EffectTargets => Parts; From 5059bfaef99e987293d6b3f3d3f5b7629dcab9d9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:17:02 +0300 Subject: [PATCH 114/304] Use FillFlowContainer in YearsPanel --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 70 +++++--------------- 1 file changed, 18 insertions(+), 52 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 2528d51331..331a7e10e1 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -11,6 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.News.Sidebar @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.News.Sidebar { private readonly Bindable metadata = new Bindable(); - private Container gridContent; + private FillFlowContainer yearsFlow; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, Bindable metadata) @@ -37,11 +37,17 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, - gridContent = new Container + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5) + Padding = new MarginPadding(5), + Child = yearsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 5) + } } }; } @@ -52,13 +58,19 @@ namespace osu.Game.Overlays.News.Sidebar metadata.BindValueChanged(m => { + yearsFlow.Clear(); + if (m.NewValue == null) { Hide(); return; } - gridContent.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); + var currentYear = m.NewValue.CurrentYear; + + foreach (var y in m.NewValue.Years) + yearsFlow.Add(new YearButton(y, y == currentYear)); + Show(); }, true); } @@ -72,8 +84,8 @@ namespace osu.Game.Overlays.News.Sidebar this.isCurrent = isCurrent; RelativeSizeAxes = Axes.X; + Width = 0.25f; Height = 15; - Padding = new MarginPadding { Vertical = 2.5f }; Child = new OsuSpriteText { @@ -92,51 +104,5 @@ namespace osu.Game.Overlays.News.Sidebar Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } - - private class YearsGridContainer : GridContainer - { - private const int column_count = 4; - - private readonly int rowCount; - - public YearsGridContainer(int[] years, int currentYear) - { - rowCount = (years.Length + column_count - 1) / column_count; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - RowDimensions = Enumerable.Range(0, rowCount).Select(_ => new Dimension(GridSizeMode.AutoSize)).ToArray(); - Content = createContent(years, currentYear); - } - - private Drawable[][] createContent(int[] years, int currentYear) - { - var buttons = new Drawable[rowCount][]; - - for (int i = 0; i < rowCount; i++) - { - buttons[i] = new Drawable[column_count]; - - for (int j = 0; j < column_count; j++) - { - var index = i * column_count + j; - - if (index >= years.Length) - { - buttons[i][j] = Empty(); - } - else - { - var year = years[index]; - var isCurrent = year == currentYear; - - buttons[i][j] = new YearButton(year, isCurrent); - } - } - } - - return buttons; - } - } } } From c0cfbd11ddabc1c4544b3b54872e4e4c3458ce14 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:20:31 +0300 Subject: [PATCH 115/304] Add tooltip and action for PostButton in MonthSection --- osu.Game/Overlays/News/Sidebar/MonthSection.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 80c408bda5..20c4d2e83e 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Diagnostics; +using osu.Framework.Platform; namespace osu.Game.Overlays.News.Sidebar { @@ -99,9 +100,12 @@ namespace osu.Game.Overlays.News.Sidebar protected override IEnumerable EffectTargets => new[] { text }; private readonly TextFlowContainer text; + private readonly APINewsPost post; public PostButton(APINewsPost post) { + this.post = post; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) @@ -113,11 +117,13 @@ namespace osu.Game.Overlays.News.Sidebar } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, GameHost host) { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // Avoid button being disabled since there's no proper action assigned. + + TooltipText = "view in browser"; + Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); } } From 586c5c7365b3d0d0291447fb57b316b1e01b098e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:36:53 +0300 Subject: [PATCH 116/304] Emulate year changes in the test scene --- .../Visual/Online/TestSceneNewsSideBar.cs | 47 +++++++++++++------ osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 5 +- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 706de2b310..376c270689 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.News.Sidebar; +using static osu.Game.Overlays.News.Sidebar.YearsPanel; namespace osu.Game.Tests.Visual.Online { @@ -18,10 +19,10 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private NewsSidebar sidebar; + private TestNewsSidebar sidebar; [SetUp] - public void SetUp() => Schedule(() => Child = sidebar = new NewsSidebar()); + public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); [Test] public void TestMetadataWithNoPosts() @@ -34,15 +35,17 @@ namespace osu.Game.Tests.Visual.Online public void TestYearsPanelVisibility() { AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); - AddStep("Add data", () => sidebar.Metadata.Value = metadata); + AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021)); AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } + private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year); + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); - private static readonly APINewsSidebar metadata = new APINewsSidebar + private APINewsSidebar getMetadata(int year) => new APINewsSidebar { - CurrentYear = 2021, + CurrentYear = year, Years = new[] { 2021, @@ -60,47 +63,47 @@ namespace osu.Game.Tests.Visual.Online new APINewsPost { Title = "(Mar) Short title", - PublishedAt = new DateTime(2021, 3, 1) + PublishedAt = new DateTime(year, 3, 1) }, new APINewsPost { Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(2021, 3, 1) + PublishedAt = new DateTime(year, 3, 1) }, new APINewsPost { Title = "(Mar) Medium title, nothing to see here", - PublishedAt = new DateTime(2021, 3, 1) + PublishedAt = new DateTime(year, 3, 1) }, new APINewsPost { Title = "(Feb) Short title", - PublishedAt = new DateTime(2021, 2, 1) + PublishedAt = new DateTime(year, 2, 1) }, new APINewsPost { Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(2021, 2, 1) + PublishedAt = new DateTime(year, 2, 1) }, new APINewsPost { Title = "(Feb) Medium title, nothing to see here", - PublishedAt = new DateTime(2021, 2, 1) + PublishedAt = new DateTime(year, 2, 1) }, new APINewsPost { Title = "Short title", - PublishedAt = new DateTime(2021, 1, 1) + PublishedAt = new DateTime(year, 1, 1) }, new APINewsPost { Title = "Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(2021, 1, 1) + PublishedAt = new DateTime(year, 1, 1) }, new APINewsPost { Title = "Medium title, nothing to see here", - PublishedAt = new DateTime(2021, 1, 1) + PublishedAt = new DateTime(year, 1, 1) } } }; @@ -123,5 +126,21 @@ namespace osu.Game.Tests.Visual.Online }, NewsPosts = Array.Empty() }; + + private class TestNewsSidebar : NewsSidebar + { + public Action YearChanged; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(m => + { + foreach (var b in this.ChildrenOfType()) + b.Action = () => YearChanged?.Invoke(b.Year); + }, true); + } + } } } diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 331a7e10e1..ffdb5cf22e 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -75,12 +75,15 @@ namespace osu.Game.Overlays.News.Sidebar }, true); } - private class YearButton : OsuHoverContainer + public class YearButton : OsuHoverContainer { + public int Year { get; } + private readonly bool isCurrent; public YearButton(int year, bool isCurrent) { + Year = year; this.isCurrent = isCurrent; RelativeSizeAxes = Axes.X; From 01090de1fd45bfd0d1a1c1b743847d089b521e93 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:55:55 +0300 Subject: [PATCH 117/304] Fix filenames does not match contained type --- .../Visual/Online/TestSceneNewsSidebar.cs | 146 ++++++++++++++++++ osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 103 ++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs create mode 100644 osu.Game/Overlays/News/Sidebar/NewsSidebar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs new file mode 100644 index 0000000000..376c270689 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -0,0 +1,146 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.News.Sidebar; +using static osu.Game.Overlays.News.Sidebar.YearsPanel; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsSidebar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private TestNewsSidebar sidebar; + + [SetUp] + public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); + + [Test] + public void TestMetadataWithNoPosts() + { + AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + } + + [Test] + public void TestYearsPanelVisibility() + { + AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); + AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021)); + AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); + } + + private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year); + + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); + + private APINewsSidebar getMetadata(int year) => new APINewsSidebar + { + CurrentYear = year, + Years = new[] + { + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = new List + { + new APINewsPost + { + Title = "(Mar) Short title", + PublishedAt = new DateTime(year, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(year, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Medium title, nothing to see here", + PublishedAt = new DateTime(year, 3, 1) + }, + new APINewsPost + { + Title = "(Feb) Short title", + PublishedAt = new DateTime(year, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(year, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Medium title, nothing to see here", + PublishedAt = new DateTime(year, 2, 1) + }, + new APINewsPost + { + Title = "Short title", + PublishedAt = new DateTime(year, 1, 1) + }, + new APINewsPost + { + Title = "Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(year, 1, 1) + }, + new APINewsPost + { + Title = "Medium title, nothing to see here", + PublishedAt = new DateTime(year, 1, 1) + } + } + }; + + private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar + { + CurrentYear = 2022, + Years = new[] + { + 2022, + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = Array.Empty() + }; + + private class TestNewsSidebar : NewsSidebar + { + public Action YearChanged; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(m => + { + foreach (var b in this.ChildrenOfType()) + b.Action = () => YearChanged?.Invoke(b.Year); + }, true); + } + } + } +} diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs new file mode 100644 index 0000000000..b8d283b7e2 --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osuTK; +using System.Linq; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class NewsSidebar : CompositeDrawable + { + [Cached] + public readonly Bindable Metadata = new Bindable(); + + private FillFlowContainer monthsFlow; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Y; + Width = 250; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Vertical = 20, + Left = 50, + Right = 30 + }, + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new YearsPanel(), + monthsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(onMetadataChanged, true); + } + + private void onMetadataChanged(ValueChangedEvent metadata) + { + monthsFlow.Clear(); + + if (metadata.NewValue == null) + return; + + var allPosts = metadata.NewValue.NewsPosts; + + if (allPosts?.Any() != true) + return; + + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); + + var keys = lookup.Select(kvp => kvp.Key); + var sortedKeys = keys.OrderByDescending(k => k).ToList(); + + var year = metadata.NewValue.CurrentYear; + + for (int i = 0; i < sortedKeys.Count; i++) + { + var month = sortedKeys[i]; + var posts = lookup[month]; + + monthsFlow.Add(new MonthSection(month, year, posts) + { + IsOpen = { Value = i == 0 } + }); + } + } + } +} From fc6e65b7dbaf9826f3a8a2832fade1a8521fb645 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 12:02:06 +0300 Subject: [PATCH 118/304] Delete TestSceneNewsSideBar.cs --- .../Visual/Online/TestSceneNewsSideBar.cs | 146 ------------------ 1 file changed, 146 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs deleted file mode 100644 index 376c270689..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ /dev/null @@ -1,146 +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 System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Testing; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; -using osu.Game.Overlays.News.Sidebar; -using static osu.Game.Overlays.News.Sidebar.YearsPanel; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneNewsSidebar : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - private TestNewsSidebar sidebar; - - [SetUp] - public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); - - [Test] - public void TestMetadataWithNoPosts() - { - AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); - } - - [Test] - public void TestYearsPanelVisibility() - { - AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); - AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021)); - AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); - } - - private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year); - - private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); - - private APINewsSidebar getMetadata(int year) => new APINewsSidebar - { - CurrentYear = year, - Years = new[] - { - 2021, - 2020, - 2019, - 2018, - 2017, - 2016, - 2015, - 2014, - 2013 - }, - NewsPosts = new List - { - new APINewsPost - { - Title = "(Mar) Short title", - PublishedAt = new DateTime(year, 3, 1) - }, - new APINewsPost - { - Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(year, 3, 1) - }, - new APINewsPost - { - Title = "(Mar) Medium title, nothing to see here", - PublishedAt = new DateTime(year, 3, 1) - }, - new APINewsPost - { - Title = "(Feb) Short title", - PublishedAt = new DateTime(year, 2, 1) - }, - new APINewsPost - { - Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(year, 2, 1) - }, - new APINewsPost - { - Title = "(Feb) Medium title, nothing to see here", - PublishedAt = new DateTime(year, 2, 1) - }, - new APINewsPost - { - Title = "Short title", - PublishedAt = new DateTime(year, 1, 1) - }, - new APINewsPost - { - Title = "Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(year, 1, 1) - }, - new APINewsPost - { - Title = "Medium title, nothing to see here", - PublishedAt = new DateTime(year, 1, 1) - } - } - }; - - private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar - { - CurrentYear = 2022, - Years = new[] - { - 2022, - 2021, - 2020, - 2019, - 2018, - 2017, - 2016, - 2015, - 2014, - 2013 - }, - NewsPosts = Array.Empty() - }; - - private class TestNewsSidebar : NewsSidebar - { - public Action YearChanged; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Metadata.BindValueChanged(m => - { - foreach (var b in this.ChildrenOfType()) - b.Action = () => YearChanged?.Invoke(b.Year); - }, true); - } - } - } -} From 555e3e2db30ac722b57a893799e7b8e63ab54550 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 12:02:33 +0300 Subject: [PATCH 119/304] Delete NewsSideBar.cs --- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 103 ------------------ 1 file changed, 103 deletions(-) delete mode 100644 osu.Game/Overlays/News/Sidebar/NewsSideBar.cs diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs deleted file mode 100644 index b8d283b7e2..0000000000 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Online.API.Requests.Responses; -using osu.Framework.Graphics.Shapes; -using osuTK; -using System.Linq; - -namespace osu.Game.Overlays.News.Sidebar -{ - public class NewsSidebar : CompositeDrawable - { - [Cached] - public readonly Bindable Metadata = new Bindable(); - - private FillFlowContainer monthsFlow; - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - RelativeSizeAxes = Axes.Y; - Width = 250; - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 20, - Left = 50, - Right = 30 - }, - Child = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - new YearsPanel(), - monthsFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) - } - } - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Metadata.BindValueChanged(onMetadataChanged, true); - } - - private void onMetadataChanged(ValueChangedEvent metadata) - { - monthsFlow.Clear(); - - if (metadata.NewValue == null) - return; - - var allPosts = metadata.NewValue.NewsPosts; - - if (allPosts?.Any() != true) - return; - - var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); - - var keys = lookup.Select(kvp => kvp.Key); - var sortedKeys = keys.OrderByDescending(k => k).ToList(); - - var year = metadata.NewValue.CurrentYear; - - for (int i = 0; i < sortedKeys.Count; i++) - { - var month = sortedKeys[i]; - var posts = lookup[month]; - - monthsFlow.Add(new MonthSection(month, year, posts) - { - IsOpen = { Value = i == 0 } - }); - } - } - } -} From 0989aa336442e0bb15f8728ed517625320297d7a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 17 May 2021 18:07:50 +0900 Subject: [PATCH 120/304] Fix accuracy heatmap points changing colour --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 88c855d768..cb769c31b8 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Statistics var point = new HitPoint(pointType, this) { - Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + BaseColour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) }; points[r][c] = point; @@ -234,6 +234,11 @@ namespace osu.Game.Rulesets.Osu.Statistics private class HitPoint : Circle { + /// + /// The base colour which will be lightened/darkened depending on the value of this . + /// + public Color4 BaseColour; + private readonly HitPointType pointType; private readonly AccuracyHeatmap heatmap; @@ -284,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Statistics Alpha = Math.Min(amount / lighten_cutoff, 1); if (pointType == HitPointType.Hit) - Colour = ((Color4)Colour).Lighten(Math.Max(0, amount - lighten_cutoff)); + Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff)); } } From 0d7a349500bfa123cf449b66b4b08f46b8a8963b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 17 May 2021 18:16:09 +0900 Subject: [PATCH 121/304] Exclude interfaces from skinnable types --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 59420bfc87..4097624400 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -57,7 +57,10 @@ namespace osu.Game.Skinning.Editor Spacing = new Vector2(20) }; - var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)).ToArray(); + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() + .Where(t => !t.IsInterface) + .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) + .ToArray(); foreach (var type in skinnableTypes) { From 1f3ae901ce979247c717245dc204da822a9c431f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:22:24 +0900 Subject: [PATCH 122/304] Expose `DrawableRuleset` for consupmtion by HUD components --- osu.Game/Screens/Play/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ac0c921ccf..7d3afe3043 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -198,6 +198,7 @@ namespace osu.Game.Screens.Play LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); + dependencies.CacheAs(DrawableRuleset); ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.ApplyBeatmap(playableBeatmap); From da0913ca2d6771049c6fc8118f82dad5d36e7142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:26:15 +0900 Subject: [PATCH 123/304] Make `SongProgress` a skinnable component --- .../Visual/Gameplay/TestSceneAutoplay.cs | 2 +- osu.Game/Screens/Play/Player.cs | 5 --- osu.Game/Screens/Play/SongProgress.cs | 42 ++++++++++++------- osu.Game/Skinning/DefaultSkin.cs | 5 +++ osu.Game/Skinning/HUDSkinComponents.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 2 + 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index e198a8504b..e47c782bca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void seekToBreak(int breakIndex) { AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime)); - AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime); + AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime); BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7d3afe3043..186e73ee4c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -358,11 +358,6 @@ namespace osu.Game.Screens.Play AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, IsCounting = false }, - RequestSeek = time => - { - GameplayClockContainer.Seek(time); - GameplayClockContainer.Start(); - }, Anchor = Anchor.Centre, Origin = Anchor.Centre }, diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 4a0787bfae..dc349ddcc6 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -14,10 +14,11 @@ using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; namespace osu.Game.Screens.Play { - public class SongProgress : OverlayContainer + public class SongProgress : OverlayContainer, ISkinnableDrawable { private const int info_height = 20; private const int bottom_bar_height = 5; @@ -39,9 +40,6 @@ namespace osu.Game.Screens.Play public readonly Bindable ShowGraph = new Bindable(); - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - private double lastHitTime => objects.Last().GetEndTime() + 1; - public override bool HandleNonPositionalInput => AllowSeeking.Value; public override bool HandlePositionalInput => AllowSeeking.Value; @@ -49,6 +47,9 @@ namespace osu.Game.Screens.Play private double firstHitTime => objects.First().StartTime; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + private double lastHitTime => objects.Last().GetEndTime() + 1; + private IEnumerable objects; public IEnumerable Objects @@ -67,10 +68,12 @@ namespace osu.Game.Screens.Play public IClock ReferenceClock; - private IClock gameplayClock; - public SongProgress() { + RelativeSizeAxes = Axes.X; + Anchor = Anchor.BottomRight; + Origin = Anchor.BottomRight; + Children = new Drawable[] { new SongProgressDisplay @@ -96,20 +99,34 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - OnSeek = time => RequestSeek?.Invoke(time), + OnSeek = seek, }, } }, }; } + private void seek(double time) + { + if (gameplayClock == null) + return; + + // TODO: implement + } + + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, GameplayClock clock, OsuConfigManager config) + private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) { base.LoadComplete(); - if (clock != null) - gameplayClock = clock; + if (drawableRuleset != null) + { + AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); + Objects = drawableRuleset.Objects; + } config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph); @@ -124,11 +141,6 @@ namespace osu.Game.Screens.Play ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); } - public void BindDrawableRuleset(DrawableRuleset drawableRuleset) - { - AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); - } - protected override void PopIn() { this.FadeIn(500, Easing.OutQuint); diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 9b4e32531a..d13ddcf22b 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Extensions; using osu.Game.IO; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -86,6 +87,7 @@ namespace osu.Game.Skinning GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)), } }; @@ -109,6 +111,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.HealthDisplay: return new DefaultHealthDisplay(); + + case HUDSkinComponents.SongProgress: + return new SongProgress(); } break; diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index a345e060e5..2e6c3a9937 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -9,5 +9,6 @@ namespace osu.Game.Skinning ScoreCounter, AccuracyCounter, HealthDisplay, + SongProgress, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a6f8f45c0f..6c8d6ee45a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -17,6 +17,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK.Graphics; @@ -350,6 +351,7 @@ namespace osu.Game.Skinning GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)) ?? new DefaultScoreCounter(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(), } }; From 0c433cda861a09d3a7e6ebaeb7fa4a15645cd48a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:26:30 +0900 Subject: [PATCH 124/304] Update `HUDOverlay` logic to add automatic layout for bottom-aligned components --- osu.Game/Screens/Play/HUDOverlay.cs | 103 ++++++++++++---------------- 1 file changed, 43 insertions(+), 60 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a10e91dae8..dcee64ff0d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,8 +35,12 @@ namespace osu.Game.Screens.Play /// public float TopScoringElementsHeight { get; private set; } + /// + /// The total height of all the bottom of screen scoring elements. + /// + public float BottomScoringElementsHeight { get; private set; } + public readonly KeyCounterDisplay KeyCounter; - public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -59,8 +63,6 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public Action RequestSeek; - private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; @@ -85,45 +87,22 @@ namespace osu.Game.Screens.Play visibilityContainer = new Container { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) { - new Drawable[] + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - // still need to be migrated; a bit more involved. - new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), - } - }, - } - }, - }, - new Drawable[] - { - Progress = CreateProgress(), + // still need to be migrated; a bit more involved. + new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), } }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize) - } - }, + } }, topRightElements = new FillFlowContainer { @@ -164,10 +143,6 @@ namespace osu.Game.Screens.Play if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); - - Progress.Objects = drawableRuleset.Objects; - Progress.RequestSeek = time => RequestSeek(time); - Progress.ReferenceClock = drawableRuleset.FrameStableClock; } ModDisplay.Current.Value = mods; @@ -206,26 +181,43 @@ namespace osu.Game.Screens.Play { base.Update(); - Vector2 lowestScreenSpace = Vector2.Zero; + Vector2? lowestTopScreenSpace = null; + Vector2? highestBottomScreenSpace = null; // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. foreach (var element in mainComponents.Components.Cast()) { // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. - if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X)) + if (!element.RelativeSizeAxes.HasFlagFast(Axes.X)) continue; - // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. - if (element is LegacyHealthDisplay) - continue; + if (element.Anchor.HasFlagFast(Anchor.TopRight)) + { + // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. + if (element is LegacyHealthDisplay) + continue; - var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; - if (bottomRight.Y > lowestScreenSpace.Y) - lowestScreenSpace = bottomRight; + var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; + if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y) + lowestTopScreenSpace = bottomRight; + } + else if (element.Anchor.HasFlagFast(Anchor.y2)) + { + var topLeft = element.ScreenSpaceDrawQuad.TopLeft; + if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y) + highestBottomScreenSpace = topLeft; + } } - topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y; - bottomRightElements.Y = -Progress.Height; + if (lowestTopScreenSpace.HasValue) + topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestTopScreenSpace.Value).Y; + else + topRightElements.Y = 0; + + if (highestBottomScreenSpace.HasValue) + bottomRightElements.Y = BottomScoringElementsHeight = -(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y); + else + bottomRightElements.Y = 0; } private void updateVisibility() @@ -281,8 +273,6 @@ namespace osu.Game.Screens.Play (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - - Progress.BindDrawableRuleset(drawableRuleset); } protected FailingLayer CreateFailingLayer() => new FailingLayer @@ -296,13 +286,6 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, }; - protected SongProgress CreateProgress() => new SongProgress - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }; - protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton { Anchor = Anchor.BottomRight, From b80768b44afc84f0d4487afdbe308c62f5ccc3a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:41:56 +0900 Subject: [PATCH 125/304] Hook up seeking flow --- osu.Game/Screens/Play/Player.cs | 6 ++++++ osu.Game/Screens/Play/SongProgress.cs | 25 +++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 186e73ee4c..d5807f6e18 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -562,6 +562,12 @@ namespace osu.Game.Screens.Play updateSampleDisabledState(); } + /// + /// Seek to a specific time in gameplay. + /// + /// The destination time to seek to. + public void Seek(double time) => GameplayClockContainer.Seek(time); + /// /// Restart gameplay via a parent . /// This can be called from a child screen in order to trigger the restart process. diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index dc349ddcc6..f95e6520cc 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -66,7 +66,13 @@ namespace osu.Game.Screens.Play } } - public IClock ReferenceClock; + [Resolved(canBeNull: true)] + private Player player { get; set; } + + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + + private IClock referenceClock; public SongProgress() { @@ -99,24 +105,13 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - OnSeek = seek, + OnSeek = time => player?.Seek(time), }, } }, }; } - private void seek(double time) - { - if (gameplayClock == null) - return; - - // TODO: implement - } - - [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } - [BackgroundDependencyLoader(true)] private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) { @@ -125,6 +120,8 @@ namespace osu.Game.Screens.Play if (drawableRuleset != null) { AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); + + referenceClock = drawableRuleset.FrameStableClock; Objects = drawableRuleset.Objects; } @@ -159,7 +156,7 @@ namespace osu.Game.Screens.Play return; double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; - double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime; + double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime; double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); From ecf70c1707b300d56c16e7cc171fa2b527284ff0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:55:18 +0900 Subject: [PATCH 126/304] Remove unnecessary container --- osu.Game/Screens/Play/SongProgress.cs | 58 +++++++++------------------ 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index f95e6520cc..b7939b5e75 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -82,32 +82,26 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - new SongProgressDisplay + info = new SongProgressInfo { - Children = new Drawable[] - { - info = new SongProgressInfo - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = info_height, - }, - graph = new SongProgressGraph - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Height = graph_height, - Margin = new MarginPadding { Bottom = bottom_bar_height }, - }, - bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - OnSeek = time => player?.Seek(time), - }, - } + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = info_height, + }, + graph = new SongProgressGraph + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Height = graph_height, + Margin = new MarginPadding { Bottom = bottom_bar_height }, + }, + bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + OnSeek = time => player?.Seek(time), }, }; } @@ -188,19 +182,5 @@ namespace osu.Game.Screens.Play float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); } - - public class SongProgressDisplay : Container - { - public SongProgressDisplay() - { - // TODO: move actual implementation into this. - // exists for skin customisation purposes (interface should be added to this container). - - Masking = true; - RelativeSizeAxes = Axes.Both; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - } - } } } From 60f3e628bcdfbf76602444f83ffa9577ef756f46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 19:05:22 +0900 Subject: [PATCH 127/304] Fix song progress being interactable inside toolbox button --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 59420bfc87..acadaa3b74 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -92,6 +92,9 @@ namespace osu.Game.Skinning.Editor private class ToolboxComponentButton : OsuButton { + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => false; + public override bool PropagateNonPositionalInputSubTree => false; + private readonly Drawable component; public Action RequestPlacement; From 42d2711dc6ca0e6cac28bd70c95d4d75272be9b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 19:29:59 +0900 Subject: [PATCH 128/304] Use `ShouldBeConsideredForInput` instead of `ReceivePositionalInputAtSubTree` --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index acadaa3b74..471ed9c6ec 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -92,7 +92,8 @@ namespace osu.Game.Skinning.Editor private class ToolboxComponentButton : OsuButton { - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => false; + protected override bool ShouldBeConsideredForInput(Drawable child) => false; + public override bool PropagateNonPositionalInputSubTree => false; private readonly Drawable component; From 7137315fa7612f02bbd963e55e6b2c6ac512b892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 19:46:50 +0900 Subject: [PATCH 129/304] Remove `HitErrorDisplay` container and hook up data --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 127 ------------------ .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 15 ++- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 4 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 11 +- osu.Game/Screens/Play/HUDOverlay.cs | 18 +-- 5 files changed, 20 insertions(+), 155 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/HitErrorDisplay.cs diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs deleted file mode 100644 index a24d9c10cb..0000000000 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD.HitErrorMeters; - -namespace osu.Game.Screens.Play.HUD -{ - public class HitErrorDisplay : Container - { - private const int fade_duration = 200; - private const int margin = 10; - - private readonly Bindable type = new Bindable(); - - private readonly HitWindows hitWindows; - - public HitErrorDisplay(HitWindows hitWindows) - { - this.hitWindows = hitWindows; - - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.ScoreMeter, type); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - type.BindValueChanged(typeChanged, true); - } - - private void typeChanged(ValueChangedEvent type) - { - Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); - - if (hitWindows == null) - return; - - switch (type.NewValue) - { - case ScoreMeterType.HitErrorBoth: - createBar(Anchor.CentreLeft); - createBar(Anchor.CentreRight); - break; - - case ScoreMeterType.HitErrorLeft: - createBar(Anchor.CentreLeft); - break; - - case ScoreMeterType.HitErrorRight: - createBar(Anchor.CentreRight); - break; - - case ScoreMeterType.HitErrorBottom: - createBar(Anchor.BottomCentre); - break; - - case ScoreMeterType.ColourBoth: - createColour(Anchor.CentreLeft); - createColour(Anchor.CentreRight); - break; - - case ScoreMeterType.ColourLeft: - createColour(Anchor.CentreLeft); - break; - - case ScoreMeterType.ColourRight: - createColour(Anchor.CentreRight); - break; - - case ScoreMeterType.ColourBottom: - createColour(Anchor.BottomCentre); - break; - } - } - - private void createBar(Anchor anchor) - { - bool rightAligned = (anchor & Anchor.x2) > 0; - bool bottomAligned = (anchor & Anchor.y2) > 0; - - var display = new BarHitErrorMeter(hitWindows, rightAligned) - { - Margin = new MarginPadding(margin), - Anchor = anchor, - Origin = bottomAligned ? Anchor.CentreLeft : anchor, - Alpha = 0, - Rotation = bottomAligned ? 270 : 0 - }; - - completeDisplayLoading(display); - } - - private void createColour(Anchor anchor) - { - bool bottomAligned = (anchor & Anchor.y2) > 0; - - var display = new ColourHitErrorMeter(hitWindows) - { - Margin = new MarginPadding(margin), - Anchor = anchor, - Origin = bottomAligned ? Anchor.CentreLeft : anchor, - Alpha = 0, - Rotation = bottomAligned ? 270 : 0 - }; - - completeDisplayLoading(display); - } - - private void completeDisplayLoading(HitErrorMeter display) - { - Add(display); - display.FadeInFromZero(fade_duration, Easing.OutQuint); - } - } -} diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 0e147f9238..c303f3889d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -43,10 +43,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private double maxHitWindow; - public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false) - : base(hitWindows) + public BarHitErrorMeter() { - alignment = rightAligned ? Anchor.x0 : Anchor.x2; + // todo: investigate. + alignment = false ? Anchor.x0 : Anchor.x2; AutoSizeAxes = Axes.Both; } @@ -152,14 +152,17 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { var windows = HitWindows.GetAllAvailableWindows().ToArray(); - maxHitWindow = windows.First().length; + // max to avoid div-by-zero. + maxHitWindow = Math.Max(1, windows.First().length); for (var i = 0; i < windows.Length; i++) { var (result, length) = windows[i]; - colourBarsEarly.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0)); - colourBarsLate.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0)); + var hitWindow = (float)(length / maxHitWindow); + + colourBarsEarly.Add(createColourBar(result, hitWindow, i == 0)); + colourBarsLate.Add(createColourBar(result, hitWindow, i == 0)); } // a little nub to mark the centre point. diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 465439cf19..0eb2367f73 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -19,8 +18,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private readonly JudgementFlow judgementsFlow; - public ColourHitErrorMeter(HitWindows hitWindows) - : base(hitWindows) + public ColourHitErrorMeter() { AutoSizeAxes = Axes.Both; InternalChild = judgementsFlow = new JudgementFlow(); diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 37e9ea43c5..b0f9928b13 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -6,13 +6,15 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { - public abstract class HitErrorMeter : CompositeDrawable + public abstract class HitErrorMeter : CompositeDrawable, ISkinnableDrawable { - protected readonly HitWindows HitWindows; + protected HitWindows HitWindows { get; private set; } [Resolved] private ScoreProcessor processor { get; set; } @@ -20,9 +22,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [Resolved] private OsuColour colours { get; set; } - protected HitErrorMeter(HitWindows hitWindows) + [BackgroundDependencyLoader(true)] + private void load(DrawableRuleset drawableRuleset) { - HitWindows = hitWindows; + HitWindows = drawableRuleset?.FirstAvailableHitWindows ?? HitWindows.Empty; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index dcee64ff0d..ab5b01cab6 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -87,22 +87,10 @@ namespace osu.Game.Screens.Play visibilityContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) { - mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - // still need to be migrated; a bit more involved. - new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), - } - }, - } + RelativeSizeAxes = Axes.Both, + }, }, topRightElements = new FillFlowContainer { From 5d5b1e1f0e441de05c7c5fda27399d05a84415f9 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 8 May 2021 11:00:22 +0200 Subject: [PATCH 130/304] Add StableImportManager --- osu.Game/Database/StableImportManager.cs | 92 +++++++++++++++++++ .../StableDirectorySelectScreen.cs | 42 +++++++++ 2 files changed, 134 insertions(+) create mode 100644 osu.Game/Database/StableImportManager.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs new file mode 100644 index 0000000000..46c6aab2fe --- /dev/null +++ b/osu.Game/Database/StableImportManager.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.IO; +using osu.Game.Overlays.Settings.Sections.Maintenance; +using osu.Game.Scoring; +using osu.Game.Skinning; + +namespace osu.Game.Database +{ + public class StableImportManager : Component + { + [Resolved] + private SkinManager skins { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Resolved] + private ScoreManager scores { get; set; } + + [Resolved] + private CollectionManager collections { get; set; } + + [Resolved] + private OsuGame game { get; set; } + + [Resolved(CanBeNull = true)] + private DesktopGameHost desktopGameHost { get; set; } + + private StableStorage cachedStorage; + + public async Task ImportFromStableAsync(StableContent content) + { + //var stableStorage = await getStableStorage().ConfigureAwait(false); + var importTasks = new List(); + + if (content.HasFlagFast(StableContent.Beatmaps)) + importTasks.Add(beatmaps.ImportFromStableAsync()); + + if (content.HasFlagFast(StableContent.Collections)) + importTasks.Add(collections.ImportFromStableAsync()); + + if (content.HasFlagFast(StableContent.Scores)) + importTasks.Add(scores.ImportFromStableAsync()); + + if (content.HasFlagFast(StableContent.Skins)) + importTasks.Add(skins.ImportFromStableAsync()); + + await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); + } + + private async Task getStableStorage() + { + var stableStorage = game.GetStorageForStableInstall(); + if (stableStorage != null) + return stableStorage; + + if (cachedStorage != null) + return cachedStorage; + + var taskCompletionSource = new TaskCompletionSource(); + Schedule(() => game.PerformFromScreen(t => t.Push(new StableDirectorySelectScreen(taskCompletionSource)))); + var stablePath = await taskCompletionSource.Task.ConfigureAwait(false); + + return cachedStorage = new StableStorage(stablePath, desktopGameHost); + } + + } + + [Flags] + public enum StableContent + { + Beatmaps = 0x1, + Scores = 0x2, + Skins = 0x3, + Collections = 0x4, + All = Beatmaps | Scores | Skins | Collections + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs new file mode 100644 index 0000000000..d935bcf526 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class StableDirectorySelectScreen : DirectorySelectScreen + { + private readonly TaskCompletionSource taskCompletionSource; + + protected override bool IsValidDirectory(DirectoryInfo info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; + + protected override OsuSpriteText CreateHeader() => new OsuSpriteText + { + Text = "Please select stable location", + Font = OsuFont.Default.With(size: 40) + }; + + public StableDirectorySelectScreen(TaskCompletionSource taskCompletionSource) + { + this.taskCompletionSource = taskCompletionSource; + } + + protected override void OnSelection(DirectoryInfo directory) + { + taskCompletionSource.TrySetResult(directory.FullName); + this.Exit(); + } + + public override bool OnBackButton() + { + taskCompletionSource.TrySetCanceled(); + return base.OnBackButton(); + } + } +} From 851e33fd1542c21dea6717c4416db71ab1866f36 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 9 May 2021 17:12:58 +0200 Subject: [PATCH 131/304] Hook up StableImportManager. --- osu.Game/Collections/CollectionManager.cs | 12 ++++----- osu.Game/Database/ArchiveModelManager.cs | 5 +--- osu.Game/Database/StableImportManager.cs | 25 ++++++++++--------- osu.Game/OsuGame.cs | 4 +++ .../Sections/Maintenance/GeneralSettings.cs | 19 +++++++------- osu.Game/Screens/Select/SongSelect.cs | 13 +++------- 6 files changed, 36 insertions(+), 42 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 9723409c79..fbd12cf672 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.IO; using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; @@ -38,8 +39,6 @@ namespace osu.Game.Collections public readonly BindableList Collections = new BindableList(); - public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; - [Resolved] private GameHost host { get; set; } @@ -104,17 +103,16 @@ namespace osu.Game.Collections /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public Task ImportFromStableAsync() + public Task ImportFromStableAsync(StableStorage stableStorage) { - var stable = GetStableStorage?.Invoke(); - if (stable == null) + if (stableStorage == null) { Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } - if (!stable.Exists(database_name)) + if (!stableStorage.Exists(database_name)) { // This handles situations like when the user does not have a collections.db file Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); @@ -123,7 +121,7 @@ namespace osu.Game.Collections return Task.Run(async () => { - using (var stream = stable.GetStream(database_name)) + using (var stream = stableStorage.GetStream(database_name)) await Import(stream).ConfigureAwait(false); }); } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e0f80d2743..38a6af4654 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -81,8 +81,6 @@ namespace osu.Game.Database public virtual IEnumerable HandledExtensions => new[] { ".zip" }; - public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop; - protected readonly FileStore Files; protected readonly IDatabaseContextFactory ContextFactory; @@ -700,9 +698,8 @@ namespace osu.Game.Database /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public Task ImportFromStableAsync() + public Task ImportFromStableAsync(StableStorage stableStorage) { - var stableStorage = GetStableStorage?.Invoke(); if (stableStorage == null) { diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 46c6aab2fe..8baf03b7a3 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -3,9 +3,8 @@ using System; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; -using JetBrains.Annotations; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; @@ -19,7 +18,7 @@ using osu.Game.Scoring; using osu.Game.Skinning; namespace osu.Game.Database -{ +{ public class StableImportManager : Component { [Resolved] @@ -42,22 +41,24 @@ namespace osu.Game.Database private StableStorage cachedStorage; + public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + public async Task ImportFromStableAsync(StableContent content) { - //var stableStorage = await getStableStorage().ConfigureAwait(false); + var stableStorage = await getStableStorage().ConfigureAwait(false); var importTasks = new List(); if (content.HasFlagFast(StableContent.Beatmaps)) - importTasks.Add(beatmaps.ImportFromStableAsync()); + importTasks.Add(beatmaps.ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(collections.ImportFromStableAsync()); + importTasks.Add(collections.ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Scores)) - importTasks.Add(scores.ImportFromStableAsync()); + importTasks.Add(scores.ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Skins)) - importTasks.Add(skins.ImportFromStableAsync()); + importTasks.Add(skins.ImportFromStableAsync(stableStorage)); await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); } @@ -83,10 +84,10 @@ namespace osu.Game.Database [Flags] public enum StableContent { - Beatmaps = 0x1, - Scores = 0x2, - Skins = 0x3, - Collections = 0x4, + Beatmaps = 1, + Scores = 2, + Skins = 4, + Collections = 8, All = Beatmaps | Scores | Skins | Collections } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f860cd8dd2..1cfe8ace43 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -100,6 +100,9 @@ namespace osu.Game [Cached] private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender(); + [Cached] + private readonly StableImportManager stableImportManager = new StableImportManager(); + [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); @@ -694,6 +697,7 @@ namespace osu.Game }, Add, true); loadComponentSingleFile(difficultyRecommender, Add); + loadComponentSingleFile(stableImportManager, Add); loadComponentSingleFile(screenshotManager, Add); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 848ce381a9..9bd360679e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; using osu.Game.Skinning; @@ -29,9 +30,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, StableImportManager stableImportManager, DialogOverlay dialogOverlay) { - if (beatmaps.SupportsImportFromStable) + if (stableImportManager.SupportsImportFromStable) { Add(importBeatmapsButton = new SettingsButton { @@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importBeatmapsButton.Enabled.Value = false; - beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); + stableImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); } @@ -57,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (scores.SupportsImportFromStable) + if (stableImportManager.SupportsImportFromStable) { Add(importScoresButton = new SettingsButton { @@ -65,7 +66,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importScoresButton.Enabled.Value = false; - scores.ImportFromStableAsync().ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + stableImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); } }); } @@ -83,7 +84,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (skins.SupportsImportFromStable) + if (stableImportManager.SupportsImportFromStable) { Add(importSkinsButton = new SettingsButton { @@ -91,7 +92,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importSkinsButton.Enabled.Value = false; - skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); + stableImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); } }); } @@ -111,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (collectionManager != null) { - if (collectionManager.SupportsImportFromStable) + if (stableImportManager.SupportsImportFromStable) { Add(importCollectionsButton = new SettingsButton { @@ -119,7 +120,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importCollectionsButton.Enabled.Value = false; - collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + stableImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 215700d87c..ff72f36c75 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; using osu.Game.Screens.Select.Options; -using osu.Game.Skinning; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -35,9 +34,9 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Collections; using osu.Game.Graphics.UserInterface; -using osu.Game.Scoring; using System.Diagnostics; using osu.Game.Screens.Play; +using osu.Game.Database; namespace osu.Game.Screens.Select { @@ -101,7 +100,7 @@ namespace osu.Game.Screens.Select private MusicController music { get; set; } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) + private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, StableImportManager stableImportManager, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) { // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); @@ -287,13 +286,7 @@ namespace osu.Game.Screens.Select { dialogOverlay.Push(new ImportFromStablePopup(() => { - Task.Run(beatmaps.ImportFromStableAsync) - .ContinueWith(_ => - { - Task.Run(scores.ImportFromStableAsync); - Task.Run(collections.ImportFromStableAsync); - }, TaskContinuationOptions.OnlyOnRanToCompletion); - Task.Run(skins.ImportFromStableAsync); + Task.Run(() => stableImportManager.ImportFromStableAsync(StableContent.All)); })); } }); From 325a689d6553b9988c7c4378103135d960600376 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 9 May 2021 18:15:21 +0200 Subject: [PATCH 132/304] Order imports depending on beatmap imports if any is running. --- osu.Game/Database/StableImportManager.cs | 25 +++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 8baf03b7a3..24b8aa9f6a 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -48,18 +48,29 @@ namespace osu.Game.Database var stableStorage = await getStableStorage().ConfigureAwait(false); var importTasks = new List(); + Task beatmapImportTask = default; if (content.HasFlagFast(StableContent.Beatmaps)) - importTasks.Add(beatmaps.ImportFromStableAsync(stableStorage)); - - if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(collections.ImportFromStableAsync(stableStorage)); - - if (content.HasFlagFast(StableContent.Scores)) - importTasks.Add(scores.ImportFromStableAsync(stableStorage)); + importTasks.Add(beatmapImportTask = beatmaps.ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Skins)) importTasks.Add(skins.ImportFromStableAsync(stableStorage)); + if (content.HasFlagFast(StableContent.Collections)) + { + if (beatmapImportTask != null) + importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + else + importTasks.Add(collections.ImportFromStableAsync(stableStorage)); + } + + if (content.HasFlagFast(StableContent.Scores)) + { + if (beatmapImportTask != null) + importTasks.Add(beatmapImportTask.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + else + importTasks.Add(scores.ImportFromStableAsync(stableStorage)); + } + await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); } From 481b0a0125c68f311d4678f910f3cbf47bb3fef1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 9 May 2021 19:50:43 +0200 Subject: [PATCH 133/304] Add StableDirectoryLocationDialog --- osu.Game/Database/StableImportManager.cs | 10 ++--- .../StableDirectoryLocationDialog.cs | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 24b8aa9f6a..09a08920dd 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -9,16 +9,16 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Platform; -using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.IO; +using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Scoring; using osu.Game.Skinning; namespace osu.Game.Database -{ +{ public class StableImportManager : Component { [Resolved] @@ -34,7 +34,7 @@ namespace osu.Game.Database private CollectionManager collections { get; set; } [Resolved] - private OsuGame game { get; set; } + private DialogOverlay dialogOverlay { get; set; } [Resolved(CanBeNull = true)] private DesktopGameHost desktopGameHost { get; set; } @@ -83,8 +83,8 @@ namespace osu.Game.Database if (cachedStorage != null) return cachedStorage; - var taskCompletionSource = new TaskCompletionSource(); - Schedule(() => game.PerformFromScreen(t => t.Push(new StableDirectorySelectScreen(taskCompletionSource)))); + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource)); var stablePath = await taskCompletionSource.Task.ConfigureAwait(false); return cachedStorage = new StableStorage(stablePath, desktopGameHost); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs new file mode 100644 index 0000000000..273ee5dc89 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Screens; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class StableDirectoryLocationDialog : PopupDialog + { + [Resolved] + private OsuGame game { get; set; } + + public StableDirectoryLocationDialog(TaskCompletionSource taskCompletionSource) + { + HeaderText = "Failed to automatically locate a stable installation."; + BodyText = "osu! failed to automatically locate a stable installation. Maybe you can tell osu! where it is located?"; + Icon = FontAwesome.Solid.QuestionCircle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Sure! I know where it is located!", + Action = () => Schedule(() => game.PerformFromScreen(screen => screen.Push(new StableDirectorySelectScreen(taskCompletionSource)))) + }, + new PopupDialogCancelButton + { + Text = "Actually I don't have osu!stable installed.", + Action = () => taskCompletionSource.TrySetCanceled() + } + }; + } + } +} From 8ba50b185445358419ed2b01508d4859e9273e8c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 9 May 2021 19:52:26 +0200 Subject: [PATCH 134/304] Bring back injected dependency incorrectly marked as unused. --- osu.Game/Database/StableImportManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 09a08920dd..e4c89062f2 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -33,6 +33,9 @@ namespace osu.Game.Database [Resolved] private CollectionManager collections { get; set; } + [Resolved] + private OsuGame game { get; set; } + [Resolved] private DialogOverlay dialogOverlay { get; set; } From a7b740fd1de6cd6211c19d52697925f6b86e79fa Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 9 May 2021 21:29:41 +0200 Subject: [PATCH 135/304] Reword ImportFromStablePopup and display the popup regardless of whether a stable install is detected. --- osu.Game/Database/StableImportManager.cs | 2 +- osu.Game/Screens/Select/ImportFromStablePopup.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index e4c89062f2..475077c54e 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -87,7 +87,7 @@ namespace osu.Game.Database return cachedStorage; var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource)); + Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource))); var stablePath = await taskCompletionSource.Task.ConfigureAwait(false); return cachedStorage = new StableStorage(stablePath, desktopGameHost); diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 8dab83b24c..e3a1505518 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select public ImportFromStablePopup(Action importFromStable) { HeaderText = @"You have no beatmaps!"; - BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins, collections and scores?\nThis will create a second copy of all files on disk."; + BodyText = "You can import files from over a stable install, though.\nWould you like to import your beatmaps, skins, collections and scores?\nThis will create a second copy of all files on disk."; Icon = FontAwesome.Solid.Plane; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ff72f36c75..d8ad752151 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -281,8 +281,8 @@ namespace osu.Game.Screens.Select { Schedule(() => { - // if we have no beatmaps but osu-stable is found, let's prompt the user to import. - if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && beatmaps.StableInstallationAvailable) + // if we have no beatmaps, let's prompt the user to import from over a stable install if he has one. + if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && stableImportManager.SupportsImportFromStable) { dialogOverlay.Push(new ImportFromStablePopup(() => { From dabe8bd4c7f358e19edc3ac57130093c170d6d64 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 10 May 2021 12:21:51 +0200 Subject: [PATCH 136/304] Fix code inspections and remove now unused code. --- osu.Game/Collections/CollectionManager.cs | 7 ------- osu.Game/Database/ArchiveModelManager.cs | 12 ------------ osu.Game/Database/StableImportManager.cs | 13 ++++--------- osu.Game/OsuGame.cs | 4 ---- .../Maintenance/StableDirectorySelectScreen.cs | 2 +- 5 files changed, 5 insertions(+), 33 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index fbd12cf672..e707b463cb 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -8,7 +8,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -95,17 +94,11 @@ namespace osu.Game.Collections /// public Action PostNotification { protected get; set; } - /// - /// Set a storage with access to an osu-stable install for import purposes. - /// - public Func GetStableStorage { private get; set; } - /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// public Task ImportFromStableAsync(StableStorage stableStorage) { - if (stableStorage == null) { Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 38a6af4654..93e2880ba0 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Humanizer; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using osu.Framework; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; @@ -667,16 +666,6 @@ namespace osu.Game.Database #region osu-stable import - /// - /// Set a storage with access to an osu-stable install for import purposes. - /// - public Func GetStableStorage { private get; set; } - - /// - /// Denotes whether an osu-stable installation is present to perform automated imports from. - /// - public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null; - /// /// The relative path from osu-stable's data directory to import items from. /// @@ -700,7 +689,6 @@ namespace osu.Game.Database /// public Task ImportFromStableAsync(StableStorage stableStorage) { - if (stableStorage == null) { Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 475077c54e..6f8225519d 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -60,18 +60,14 @@ namespace osu.Game.Database if (content.HasFlagFast(StableContent.Collections)) { - if (beatmapImportTask != null) - importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); - else - importTasks.Add(collections.ImportFromStableAsync(stableStorage)); + importTasks.Add(beatmapImportTask?.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion) + ?? collections.ImportFromStableAsync(stableStorage)); } if (content.HasFlagFast(StableContent.Scores)) { - if (beatmapImportTask != null) - importTasks.Add(beatmapImportTask.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); - else - importTasks.Add(scores.ImportFromStableAsync(stableStorage)); + importTasks.Add(beatmapImportTask?.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion) + ?? scores.ImportFromStableAsync(stableStorage)); } await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); @@ -92,7 +88,6 @@ namespace osu.Game.Database return cachedStorage = new StableStorage(stablePath, desktopGameHost); } - } [Flags] diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1cfe8ace43..06e0b6e9bf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -569,14 +569,11 @@ namespace osu.Game // todo: all archive managers should be able to be looped here. SkinManager.PostNotification = n => notifications.Post(n); - SkinManager.GetStableStorage = GetStorageForStableInstall; BeatmapManager.PostNotification = n => notifications.Post(n); - BeatmapManager.GetStableStorage = GetStorageForStableInstall; BeatmapManager.PresentImport = items => PresentBeatmap(items.First()); ScoreManager.PostNotification = n => notifications.Post(n); - ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.PresentImport = items => PresentScore(items.First()); // make config aware of how to lookup skins for on-screen display purposes. @@ -693,7 +690,6 @@ namespace osu.Game loadComponentSingleFile(new CollectionManager(Storage) { PostNotification = n => notifications.Post(n), - GetStableStorage = GetStorageForStableInstall }, Add, true); loadComponentSingleFile(difficultyRecommender, Add); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index d935bcf526..6065122545 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } protected override void OnSelection(DirectoryInfo directory) - { + { taskCompletionSource.TrySetResult(directory.FullName); this.Exit(); } From e15e8068d3bd33170fd19497f2d2d3a4a73f8365 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 12:09:37 +0200 Subject: [PATCH 137/304] Reword StableDirectoryLocationDialog. Co-authored-by: Dean Herbert --- .../Sections/Maintenance/StableDirectoryLocationDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs index 273ee5dc89..298f7d2433 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public StableDirectoryLocationDialog(TaskCompletionSource taskCompletionSource) { - HeaderText = "Failed to automatically locate a stable installation."; + HeaderText = "Failed to automatically locate an osu!stable installation."; BodyText = "osu! failed to automatically locate a stable installation. Maybe you can tell osu! where it is located?"; Icon = FontAwesome.Solid.QuestionCircle; From f60dbbfbbd2b53cb0d1207b82333b89bbb77ceef Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 12:10:26 +0200 Subject: [PATCH 138/304] Reword import dialogs. Co-authored-by: Dean Herbert --- .../Sections/Maintenance/StableDirectoryLocationDialog.cs | 2 +- osu.Game/Screens/Select/ImportFromStablePopup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs index 298f7d2433..904c9deaae 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public StableDirectoryLocationDialog(TaskCompletionSource taskCompletionSource) { HeaderText = "Failed to automatically locate an osu!stable installation."; - BodyText = "osu! failed to automatically locate a stable installation. Maybe you can tell osu! where it is located?"; + BodyText = "An existing install could not be located. If you know where it is, you can help locate it."; Icon = FontAwesome.Solid.QuestionCircle; Buttons = new PopupDialogButton[] diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index e3a1505518..d8137432bd 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select public ImportFromStablePopup(Action importFromStable) { HeaderText = @"You have no beatmaps!"; - BodyText = "You can import files from over a stable install, though.\nWould you like to import your beatmaps, skins, collections and scores?\nThis will create a second copy of all files on disk."; + BodyText = "Would you like to import your beatmaps, skins, collections and scores from an existing osu!stable installation?\nThis will create a second copy of all files on disk."; Icon = FontAwesome.Solid.Plane; From bec06cfac7827423bbea001dd9cd5fd039b8bb09 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 15:17:33 +0200 Subject: [PATCH 139/304] Reword `StableDirectoryLocationDialog` header Co-authored-by: Salman Ahmed --- .../Sections/Maintenance/StableDirectorySelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 6065122545..f7c7934c63 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override OsuSpriteText CreateHeader() => new OsuSpriteText { - Text = "Please select stable location", + Text = "Please select your osu!stable install location", Font = OsuFont.Default.With(size: 40) }; From 41fafdf643f1a3858907559ff4bddbc5fa538f00 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 14 May 2021 17:24:36 +0200 Subject: [PATCH 140/304] Remove now unreachable code paths. --- osu.Game/Collections/CollectionManager.cs | 6 ------ osu.Game/Database/ArchiveModelManager.cs | 6 ------ 2 files changed, 12 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index e707b463cb..3a63587b30 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -99,12 +99,6 @@ namespace osu.Game.Collections /// public Task ImportFromStableAsync(StableStorage stableStorage) { - if (stableStorage == null) - { - Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - if (!stableStorage.Exists(database_name)) { // This handles situations like when the user does not have a collections.db file diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 93e2880ba0..550daf36b5 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -689,12 +689,6 @@ namespace osu.Game.Database /// public Task ImportFromStableAsync(StableStorage stableStorage) { - if (stableStorage == null) - { - Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - var storage = PrepareStableStorage(stableStorage); if (!storage.ExistsDirectory(ImportFromStablePath)) From fe11426238eaf2713414ddc63b0499df4c4b66ba Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 16 May 2021 15:35:44 +0200 Subject: [PATCH 141/304] Disable appearance of the stable import prompt waiting for user interaction in tests, which caused them to fail. --- .../Navigation/TestScenePerformFromScreen.cs | 21 ++++++++++++------- osu.Game/Screens/Select/SongSelect.cs | 4 +++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 2791952b66..078bb817f8 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -37,17 +37,17 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtSongSelect() { - PushAndConfirm(() => new PlaySongSelect()); + PushAndConfirm(() => new TestPlaySongSelect()); - AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) })); + AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) })); AddAssert("did perform", () => actionPerformed); - AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect); } [Test] public void TestPerformAtMenuFromSongSelect() { - PushAndConfirm(() => new PlaySongSelect()); + PushAndConfirm(() => new TestPlaySongSelect()); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu); @@ -57,18 +57,18 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtSongSelectFromPlayerLoader() { - PushAndConfirm(() => new PlaySongSelect()); + PushAndConfirm(() => new TestPlaySongSelect()); PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); - AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) })); - AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) })); + AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect); AddAssert("did perform", () => actionPerformed); } [Test] public void TestPerformAtMenuFromPlayerLoader() { - PushAndConfirm(() => new PlaySongSelect()); + PushAndConfirm(() => new TestPlaySongSelect()); PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); @@ -187,5 +187,10 @@ namespace osu.Game.Tests.Visual.Navigation return base.OnExiting(next); } } + + public class TestPlaySongSelect : PlaySongSelect + { + protected override bool DisplayStableImportPrompt => false; + } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d8ad752151..5a48ee7606 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; + protected virtual bool DisplayStableImportPrompt => true; + /// /// Can be null if is false. /// @@ -282,7 +284,7 @@ namespace osu.Game.Screens.Select Schedule(() => { // if we have no beatmaps, let's prompt the user to import from over a stable install if he has one. - if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && stableImportManager.SupportsImportFromStable) + if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && stableImportManager.SupportsImportFromStable && DisplayStableImportPrompt) { dialogOverlay.Push(new ImportFromStablePopup(() => { From ed4c025c7e79669b198eb331e8d6fcc20ec04a9e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 16 May 2021 17:14:23 +0200 Subject: [PATCH 142/304] Fix other tests and move TestPlaySongSelect class declaration. --- .../Navigation/TestScenePerformFromScreen.cs | 7 +--- .../Navigation/TestSceneScreenNavigation.cs | 34 ++++++++++--------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 078bb817f8..3cedaf9d45 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -11,8 +11,8 @@ using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; -using osu.Game.Screens.Select; using osuTK.Input; +using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; namespace osu.Game.Tests.Visual.Navigation { @@ -187,10 +187,5 @@ namespace osu.Game.Tests.Visual.Navigation return base.OnExiting(next); } } - - public class TestPlaySongSelect : PlaySongSelect - { - protected override bool DisplayStableImportPrompt => false; - } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 859cefe3a9..253e448bb4 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitSongSelectWithEscape() { - TestSongSelect songSelect = null; + TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestSongSelect()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); pushEscape(); @@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestOpenModSelectOverlayUsingAction() { - TestSongSelect songSelect = null; + TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestSongSelect()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show mods overlay", () => InputManager.Key(Key.F1)); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); } @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation { Player player = null; - PushAndConfirm(() => new TestSongSelect()); + PushAndConfirm(() => new TestPlaySongSelect()); AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Navigation WorkingBeatmap beatmap() => Game.Beatmap.Value; - PushAndConfirm(() => new TestSongSelect()); + PushAndConfirm(() => new TestPlaySongSelect()); AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Navigation WorkingBeatmap beatmap() => Game.Beatmap.Value; - PushAndConfirm(() => new TestSongSelect()); + PushAndConfirm(() => new TestPlaySongSelect()); AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); @@ -139,9 +139,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestMenuMakesMusic() { - TestSongSelect songSelect = null; + TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestSongSelect()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice); @@ -153,9 +153,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitSongSelectWithClick() { - TestSongSelect songSelect = null; + TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestSongSelect()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); @@ -213,9 +213,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestModSelectInput() { - TestSongSelect songSelect = null; + TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestSongSelect()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); @@ -234,9 +234,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestBeatmapOptionsInput() { - TestSongSelect songSelect = null; + TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestSongSelect()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show()); @@ -312,11 +312,13 @@ namespace osu.Game.Tests.Visual.Navigation ConfirmAtMainMenu(); } - private class TestSongSelect : PlaySongSelect + public class TestPlaySongSelect : PlaySongSelect { public ModSelectOverlay ModSelectOverlay => ModSelect; public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions; + + protected override bool DisplayStableImportPrompt => false; } } } From db255e6814fa463c2a69893bde37cbcde0b58576 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 16 May 2021 17:49:49 +0200 Subject: [PATCH 143/304] Mark StableImportManager as potentially null in tests. (StableImportManager is added to the DI in OsuGame and not in the OsuGameBase) --- .../Settings/Sections/Maintenance/GeneralSettings.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 9bd360679e..20fc0e962a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -30,9 +30,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, StableImportManager stableImportManager, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay) { - if (stableImportManager.SupportsImportFromStable) + if (stableImportManager?.SupportsImportFromStable ?? false) { Add(importBeatmapsButton = new SettingsButton { @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (stableImportManager.SupportsImportFromStable) + if (stableImportManager?.SupportsImportFromStable ?? false) { Add(importScoresButton = new SettingsButton { @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (stableImportManager.SupportsImportFromStable) + if (stableImportManager?.SupportsImportFromStable ?? false) { Add(importSkinsButton = new SettingsButton { @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (collectionManager != null) { - if (stableImportManager.SupportsImportFromStable) + if (stableImportManager?.SupportsImportFromStable ?? false) { Add(importCollectionsButton = new SettingsButton { From a38fc1a2e0bff4bcd36e34e926be4c98d3fcf7e3 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 13:04:49 +0200 Subject: [PATCH 144/304] Override text header. --- .../Sections/Maintenance/StableDirectorySelectScreen.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index f7c7934c63..4ea53a3fc1 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -4,9 +4,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Localisation; using osu.Framework.Screens; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -16,11 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override bool IsValidDirectory(DirectoryInfo info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; - protected override OsuSpriteText CreateHeader() => new OsuSpriteText - { - Text = "Please select your osu!stable install location", - Font = OsuFont.Default.With(size: 40) - }; + public override LocalisableString HeaderText => "Please select your osu!stable install location"; public StableDirectorySelectScreen(TaskCompletionSource taskCompletionSource) { From 5ca4fd5ab4f53c61f88a4900b97c6c4150fb43b0 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 13:28:59 +0200 Subject: [PATCH 145/304] Block overlays to prevent getting into a bad state. --- .../Sections/Maintenance/StableDirectorySelectScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 4ea53a3fc1..4aea05fb14 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -13,6 +13,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private readonly TaskCompletionSource taskCompletionSource; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + protected override bool IsValidDirectory(DirectoryInfo info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; public override LocalisableString HeaderText => "Please select your osu!stable install location"; @@ -28,10 +30,10 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance this.Exit(); } - public override bool OnBackButton() + public override bool OnExiting(IScreen next) { taskCompletionSource.TrySetCanceled(); - return base.OnBackButton(); + return base.OnExiting(next); } } } From 6110a847aa82a2e0dd5c0bf90aa8c1db6bee1399 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 16:30:13 +0200 Subject: [PATCH 146/304] Simplify import ordering logic by making beatmapImportTask non-nullable. --- osu.Game/Database/StableImportManager.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 6f8225519d..67f91d3bdb 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -51,7 +51,7 @@ namespace osu.Game.Database var stableStorage = await getStableStorage().ConfigureAwait(false); var importTasks = new List(); - Task beatmapImportTask = default; + Task beatmapImportTask = Task.CompletedTask; if (content.HasFlagFast(StableContent.Beatmaps)) importTasks.Add(beatmapImportTask = beatmaps.ImportFromStableAsync(stableStorage)); @@ -59,16 +59,10 @@ namespace osu.Game.Database importTasks.Add(skins.ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - { - importTasks.Add(beatmapImportTask?.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion) - ?? collections.ImportFromStableAsync(stableStorage)); - } + importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) - { - importTasks.Add(beatmapImportTask?.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion) - ?? scores.ImportFromStableAsync(stableStorage)); - } + importTasks.Add(beatmapImportTask.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); } From 97952bc3f0fd105535999cd47a3f280640fa4c73 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 18:39:04 +0200 Subject: [PATCH 147/304] Fix backwards stable install resolution logic. --- osu.Game/Database/StableImportManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 67f91d3bdb..331764f274 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -69,13 +69,13 @@ namespace osu.Game.Database private async Task getStableStorage() { - var stableStorage = game.GetStorageForStableInstall(); - if (stableStorage != null) - return stableStorage; - if (cachedStorage != null) return cachedStorage; + var stableStorage = game.GetStorageForStableInstall(); + if (stableStorage != null) + return cachedStorage = stableStorage; + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource))); var stablePath = await taskCompletionSource.Task.ConfigureAwait(false); From 7befcf74ffbb54fc4783d22f9235667db6c0e1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 18:53:09 +0200 Subject: [PATCH 148/304] Split value change callbacks out to separate methods --- .../Overlays/News/Sidebar/MonthSection.cs | 36 ++++++++++--------- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 28 ++++++++------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 20c4d2e83e..77f09b750d 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -153,28 +153,30 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - IsOpen.BindValueChanged(open => - { - ClearTransforms(true); - - if (open.NewValue) - { - AutoSizeAxes = Axes.Y; - content.FadeIn(animation_duration, Easing.OutQuint); - } - else - { - AutoSizeAxes = Axes.None; - this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); - - content.FadeOut(animation_duration, Easing.OutQuint); - } - }, true); + IsOpen.BindValueChanged(_ => updateState(), true); // First state change should be instant. FinishTransforms(true); } + private void updateState() + { + ClearTransforms(true); + + if (IsOpen.Value) + { + AutoSizeAxes = Axes.Y; + content.FadeIn(animation_duration, Easing.OutQuint); + } + else + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); + + content.FadeOut(animation_duration, Easing.OutQuint); + } + } + private bool shouldUpdateAutosize = true; // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index ffdb5cf22e..849cdbf659 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -56,23 +56,25 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - metadata.BindValueChanged(m => + metadata.BindValueChanged(_ => recreateDrawables(), true); + } + + private void recreateDrawables() + { + yearsFlow.Clear(); + + if (metadata.Value == null) { - yearsFlow.Clear(); + Hide(); + return; + } - if (m.NewValue == null) - { - Hide(); - return; - } + var currentYear = metadata.Value.CurrentYear; - var currentYear = m.NewValue.CurrentYear; + foreach (var y in metadata.Value.Years) + yearsFlow.Add(new YearButton(y, y == currentYear)); - foreach (var y in m.NewValue.Years) - yearsFlow.Add(new YearButton(y, y == currentYear)); - - Show(); - }, true); + Show(); } public class YearButton : OsuHoverContainer From d614a47614218d9a6bc1406736ced7e2937d41e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 18:54:17 +0200 Subject: [PATCH 149/304] Rename variable to better explain purpose --- osu.Game/Overlays/News/Sidebar/MonthSection.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 77f09b750d..166da97f93 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -177,19 +177,19 @@ namespace osu.Game.Overlays.News.Sidebar } } - private bool shouldUpdateAutosize = true; + private bool autoSizeTransitionApplied; - // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. + // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutoSize{Duration,Easing}. protected override void UpdateAfterAutoSize() { base.UpdateAfterAutoSize(); - if (shouldUpdateAutosize) + if (!autoSizeTransitionApplied) { AutoSizeDuration = animation_duration; AutoSizeEasing = Easing.OutQuint; - shouldUpdateAutosize = false; + autoSizeTransitionApplied = true; } } } From 400984457ca08221dbb99178fc6dcba920311a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 19:16:30 +0200 Subject: [PATCH 150/304] Fix weird behaviour in test scene Due to a callback set up in another place, clicking away from the 2022 year after launching the test scene would remove the 2022 button (because the callback was returning metadata without it). For simplicity just trim the 2022 year to make sure both test scenes use the same consistent set of years. --- osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index 376c270689..6cd3bd7d51 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -110,10 +110,9 @@ namespace osu.Game.Tests.Visual.Online private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar { - CurrentYear = 2022, + CurrentYear = 2021, Years = new[] { - 2022, 2021, 2020, 2019, From e2018f81f3c2f59d46e9261b0518d01677277625 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 19:54:21 +0200 Subject: [PATCH 151/304] Use equality check for nullable types. --- .../Settings/Sections/Maintenance/GeneralSettings.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 20fc0e962a..a38ca81e23 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay) { - if (stableImportManager?.SupportsImportFromStable ?? false) + if (stableImportManager?.SupportsImportFromStable == true) { Add(importBeatmapsButton = new SettingsButton { @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (stableImportManager?.SupportsImportFromStable ?? false) + if (stableImportManager?.SupportsImportFromStable == true) { Add(importScoresButton = new SettingsButton { @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (stableImportManager?.SupportsImportFromStable ?? false) + if (stableImportManager?.SupportsImportFromStable == true) { Add(importSkinsButton = new SettingsButton { @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (collectionManager != null) { - if (stableImportManager?.SupportsImportFromStable ?? false) + if (stableImportManager?.SupportsImportFromStable == true) { Add(importCollectionsButton = new SettingsButton { From 8530b31e39ca3eb5d6e275cc5f88ae9d5baf8efa Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 21:02:45 +0200 Subject: [PATCH 152/304] Use bitshifts for enum values instead of literal values. --- osu.Game/Database/StableImportManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 331764f274..63a6db35c0 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -87,10 +87,10 @@ namespace osu.Game.Database [Flags] public enum StableContent { - Beatmaps = 1, - Scores = 2, - Skins = 4, - Collections = 8, + Beatmaps = 1 << 0, + Scores = 1 << 1, + Skins = 1 << 2, + Collections = 1 << 3, All = Beatmaps | Scores | Skins | Collections } } From 79740dd2d8f70645becc7edf5134b3ee3a7041f2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 22:01:05 +0200 Subject: [PATCH 153/304] Merge conditionnal expression. --- osu.Game/Screens/Select/SongSelect.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5a48ee7606..729e25203f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; - protected virtual bool DisplayStableImportPrompt => true; + protected virtual bool DisplayStableImportPrompt => stableImportManager.SupportsImportFromStable; /// /// Can be null if is false. @@ -85,6 +85,9 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private StableImportManager stableImportManager { get; set; } + protected ModSelectOverlay ModSelect { get; private set; } protected Sample SampleConfirm { get; private set; } @@ -102,7 +105,7 @@ namespace osu.Game.Screens.Select private MusicController music { get; set; } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, StableImportManager stableImportManager, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) + private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) { // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); @@ -284,7 +287,7 @@ namespace osu.Game.Screens.Select Schedule(() => { // if we have no beatmaps, let's prompt the user to import from over a stable install if he has one. - if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && stableImportManager.SupportsImportFromStable && DisplayStableImportPrompt) + if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && DisplayStableImportPrompt) { dialogOverlay.Push(new ImportFromStablePopup(() => { From 9d423245d8b797f6adad68afd718bda103202d19 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 18 May 2021 13:02:23 +0900 Subject: [PATCH 154/304] Fix up xmldocs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index f59ad8bfa2..c7df2d1a76 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index e32dcc81ee..8a052299ce 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { From 06389c08dc19287fdece6019170de4257365e7d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 13:11:22 +0900 Subject: [PATCH 155/304] Add basic test to show data how one would expect it to be displayed --- osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index 6cd3bd7d51..b000553a7b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -24,11 +24,18 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); + [Test] + public void TestBasic() + { + AddStep("Add metadata", () => sidebar.Metadata.Value = getMetadata(2021)); + AddUntilStep("Month sections exist", () => sidebar.ChildrenOfType().Any()); + } + [Test] public void TestMetadataWithNoPosts() { AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType().Any()); } [Test] @@ -134,7 +141,7 @@ namespace osu.Game.Tests.Visual.Online { base.LoadComplete(); - Metadata.BindValueChanged(m => + Metadata.BindValueChanged(metadata => { foreach (var b in this.ChildrenOfType()) b.Action = () => YearChanged?.Invoke(b.Year); From f1f3606fd0ce9e4fea87b72faa1b2697b226ab45 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 13:11:58 +0900 Subject: [PATCH 156/304] Fix unresolved xmldocs --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index c7df2d1a76..12ab89f79e 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 8a052299ce..2b71bb2f16 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { From c71d53a0f9ce174dc65640879df256a55397b1c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 13:40:26 +0900 Subject: [PATCH 157/304] Fix text and button layout --- .../Maintenance/DirectorySelectScreen.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index e7c69e89fe..349a112477 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -11,9 +11,9 @@ using osuTK; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Framework.Screens; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -69,20 +69,24 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance RelativeSizeAxes = Axes.Both, RowDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(), - new Dimension(GridSizeMode.Relative, 0.8f), - new Dimension(), + new Dimension(GridSizeMode.AutoSize), }, Content = new[] { new Drawable[] { - new OsuSpriteText + new OsuTextFlowContainer(cp => { - Text = HeaderText, - Font = OsuFont.Default.With(size: 40), - Origin = Anchor.Centre, - Anchor = Anchor.Centre, + cp.Font = OsuFont.Default.With(size: 24); + }) + { + Text = HeaderText.ToString(), + TextAnchor = Anchor.TopCentre, + Margin = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, } }, new Drawable[] @@ -99,6 +103,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300, + Margin = new MarginPadding(10), Text = "Select directory", Action = () => OnSelection(directorySelector.CurrentPath.Value) }, From e621cfc4ea6f3656ac47cca8ad61204e09cab8c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:14:10 +0900 Subject: [PATCH 158/304] Add Apply() method for applying new DHOs --- .../Blueprints/HoldNoteSelectionBlueprint.cs | 21 ++++++++++++------- .../Sliders/SliderSelectionBlueprint.cs | 9 ++++++++ .../Edit/HitObjectSelectionBlueprint.cs | 20 +++++++++++++++++- .../Components/ComposeBlueprintContainer.cs | 2 +- .../Visual/SelectionBlueprintTestScene.cs | 2 +- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index c7ee6097c6..ac821e504c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -21,6 +22,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly IBindable direction = new Bindable(); + private HoldNoteNoteSelectionBlueprint headBlueprint; + private HoldNoteNoteSelectionBlueprint tailBlueprint; + [Resolved] private OsuColour colours { get; set; } @@ -33,16 +37,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private void load(IScrollingInfo scrollingInfo) { direction.BindTo(scrollingInfo.Direction); - } - - protected override void LoadComplete() - { - base.LoadComplete(); InternalChildren = new Drawable[] { - new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), - new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), + headBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), + tailBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), new Container { RelativeSizeAxes = Axes.Both, @@ -59,6 +58,14 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }; } + public override void Apply(DrawableHitObject drawableObject) + { + base.Apply(drawableObject); + + headBlueprint?.Apply(drawableObject); + tailBlueprint?.Apply(drawableObject); + } + protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 939f4cdc3e..21945853a8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -14,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -77,6 +78,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + public override void Apply(DrawableHitObject drawableObject) + { + base.Apply(drawableObject); + + HeadBlueprint?.Apply(drawableObject); + TailBlueprint?.Apply(drawableObject); + } + public override bool HandleQuickDeletion() { var hoveredControlPoint = ControlPointVisualiser?.Pieces.FirstOrDefault(p => p.IsHovered); diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 56434b1d82..6e9172c822 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// /// The which this applies to. /// - public DrawableHitObject DrawableObject { get; internal set; } + public virtual DrawableHitObject DrawableObject { get; private set; } /// /// Whether the blueprint should be shown even when the is not alive. @@ -28,6 +28,24 @@ namespace osu.Game.Rulesets.Edit { } + protected override void LoadAsyncComplete() + { + // Must be done before base.LoadAsyncComplete() as this may affect children. + Apply(DrawableObject); + + base.LoadAsyncComplete(); + } + + /// + /// Applies a to this . + /// The represented model does not change. + /// + /// The new . + public virtual void Apply(DrawableHitObject drawableObject) + { + DrawableObject = drawableObject; + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 95f4069edb..b3773b9ec1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); + return CreateHitObjectBlueprintFor(item).With(b => b.Apply(drawable)); } public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index dc12a4999d..b518465c40 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual { Add(blueprint.With(d => { - d.DrawableObject = drawableObject; + d.Apply(drawableObject); d.Depth = float.MinValue; d.Select(); })); From 532c41c82eb8f0640f6ac58f24f00623784c9371 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:19:11 +0900 Subject: [PATCH 159/304] Remove nested blueprints from sliders --- .../TestSceneSliderControlPointPiece.cs | 10 +++++----- .../TestSceneSliderSelectionBlueprint.cs | 14 +++++++------- ...ionBlueprint.cs => SliderCircleOverlay.cs} | 17 +++++++---------- .../Sliders/SliderSelectionBlueprint.cs | 19 +++++-------------- 4 files changed, 24 insertions(+), 36 deletions(-) rename osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/{SliderCircleSelectionBlueprint.cs => SliderCircleOverlay.cs} (53%) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index fe0f2f8a87..24b947c854 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -150,8 +150,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; - public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; - public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -159,14 +159,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); } - private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint + private class TestSliderCircleOverlay : SliderCircleOverlay { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(Slider slider, SliderPosition position) + public TestSliderCircleOverlay(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 721bb1985d..0d828a79c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -174,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition); AddAssert("head positioned correctly", - () => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); + () => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); AddAssert("tail positioned correctly", - () => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); + () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } private void moveMouseToControlPoint(int index) @@ -195,8 +195,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; - public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; - public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -204,14 +204,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); } - private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint + private class TestSliderCircleOverlay : SliderCircleOverlay { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(Slider slider, SliderPosition position) + public TestSliderCircleOverlay(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs similarity index 53% rename from osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index ff01c7a442..241ff70a18 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -1,35 +1,32 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Containers; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint + public class SliderCircleOverlay : CompositeDrawable { protected readonly HitCirclePiece CirclePiece; + private readonly Slider slider; private readonly SliderPosition position; - public SliderCircleSelectionBlueprint(Slider slider, SliderPosition position) - : base(slider) + public SliderCircleOverlay(Slider slider, SliderPosition position) { + this.slider = slider; this.position = position; InternalChild = CirclePiece = new HitCirclePiece(); - - Select(); } protected override void Update() { base.Update(); - CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)HitObject.HeadCircle : HitObject.TailCircle); + CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle); } - - // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. - public override bool HandlePositionalInput => false; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 21945853a8..ec97f1fd78 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -14,7 +14,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -27,8 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public class SliderSelectionBlueprint : OsuSelectionBlueprint { protected SliderBodyPiece BodyPiece { get; private set; } - protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } - protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } + protected SliderCircleOverlay HeadOverlay { get; private set; } + protected SliderCircleOverlay TailOverlay { get; private set; } [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } @@ -61,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders InternalChildren = new Drawable[] { BodyPiece = new SliderBodyPiece(), - HeadBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.Start), - TailBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.End), + HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), + TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End), }; } @@ -78,14 +77,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } - public override void Apply(DrawableHitObject drawableObject) - { - base.Apply(drawableObject); - - HeadBlueprint?.Apply(drawableObject); - TailBlueprint?.Apply(drawableObject); - } - public override bool HandleQuickDeletion() { var hoveredControlPoint = ControlPointVisualiser?.Pieces.FirstOrDefault(p => p.IsHovered); @@ -250,6 +241,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; - protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); + protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position); } } From 72beddaadc2799ab2a0c72f2b9a7aae81b195a82 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:25:07 +0900 Subject: [PATCH 160/304] Remove nested blueprints from hold notes --- .../Editor/TestSceneManiaHitObjectComposer.cs | 4 ++-- ...ionBlueprint.cs => HoldNoteNoteOverlay.cs} | 23 ++++++++----------- .../Blueprints/HoldNoteSelectionBlueprint.cs | 16 ++----------- 3 files changed, 14 insertions(+), 29 deletions(-) rename osu.Game.Rulesets.Mania/Edit/Blueprints/{HoldNoteNoteSelectionBlueprint.cs => HoldNoteNoteOverlay.cs} (60%) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index aaf96c63a6..8474279b01 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -184,8 +184,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft)); AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); - AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); - AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); + AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); + AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); } private void setScrollStep(ScrollingDirection direction) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs similarity index 60% rename from osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs index 14c8d488a9..6933571be8 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs @@ -2,35 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint + public class HoldNoteNoteOverlay : CompositeDrawable { - protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; - + private readonly HoldNoteSelectionBlueprint holdNoteBlueprint; private readonly HoldNotePosition position; - public HoldNoteNoteSelectionBlueprint(HoldNote holdNote, HoldNotePosition position) - : base(holdNote) + public HoldNoteNoteOverlay(HoldNoteSelectionBlueprint holdNoteBlueprint, HoldNotePosition position) { + this.holdNoteBlueprint = holdNoteBlueprint; this.position = position; - InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; - Select(); + InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; } protected override void Update() { base.Update(); + var drawableObject = holdNoteBlueprint.DrawableObject; + // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. - if (DrawableObject.IsLoaded) + if (drawableObject.IsLoaded) { - DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail; + DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)drawableObject.Head : drawableObject.Tail; Anchor = note.Anchor; Origin = note.Origin; @@ -39,8 +39,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints Position = note.DrawPosition; } } - - // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. - public override bool HandlePositionalInput => false; } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index ac821e504c..d04c5cd4aa 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -22,9 +21,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly IBindable direction = new Bindable(); - private HoldNoteNoteSelectionBlueprint headBlueprint; - private HoldNoteNoteSelectionBlueprint tailBlueprint; - [Resolved] private OsuColour colours { get; set; } @@ -40,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChildren = new Drawable[] { - headBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), - tailBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), + new HoldNoteNoteOverlay(this, HoldNotePosition.Start), + new HoldNoteNoteOverlay(this, HoldNotePosition.End), new Container { RelativeSizeAxes = Axes.Both, @@ -58,14 +54,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }; } - public override void Apply(DrawableHitObject drawableObject) - { - base.Apply(drawableObject); - - headBlueprint?.Apply(drawableObject); - tailBlueprint?.Apply(drawableObject); - } - protected override void Update() { base.Update(); From 882d54a8f8692cdbd508614b7033cff898bd070d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:26:26 +0900 Subject: [PATCH 161/304] Remove now unnecessary Apply() method --- .../Edit/HitObjectSelectionBlueprint.cs | 20 +------------------ .../Components/ComposeBlueprintContainer.cs | 2 +- .../Visual/SelectionBlueprintTestScene.cs | 2 +- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 6e9172c822..56434b1d82 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// /// The which this applies to. /// - public virtual DrawableHitObject DrawableObject { get; private set; } + public DrawableHitObject DrawableObject { get; internal set; } /// /// Whether the blueprint should be shown even when the is not alive. @@ -28,24 +28,6 @@ namespace osu.Game.Rulesets.Edit { } - protected override void LoadAsyncComplete() - { - // Must be done before base.LoadAsyncComplete() as this may affect children. - Apply(DrawableObject); - - base.LoadAsyncComplete(); - } - - /// - /// Applies a to this . - /// The represented model does not change. - /// - /// The new . - public virtual void Apply(DrawableHitObject drawableObject) - { - DrawableObject = drawableObject; - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index b3773b9ec1..95f4069edb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateHitObjectBlueprintFor(item).With(b => b.Apply(drawable)); + return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); } public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index b518465c40..dc12a4999d 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual { Add(blueprint.With(d => { - d.Apply(drawableObject); + d.DrawableObject = drawableObject; d.Depth = float.MinValue; d.Select(); })); From 829d326e36cdab8a4a1be3e9624a0003d4910d48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 14:50:10 +0900 Subject: [PATCH 162/304] Remove alignment logic completely for the time being This was overly complex and does not play well with the new layout customisation system. We can add it back as required. --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index c303f3889d..32d7f3525f 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -20,8 +20,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { - private readonly Anchor alignment; - private const int arrow_move_duration = 400; private const int judgement_line_width = 6; @@ -45,9 +43,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public BarHitErrorMeter() { - // todo: investigate. - alignment = false ? Anchor.x0 : Anchor.x2; - AutoSizeAxes = Axes.Both; } @@ -63,33 +58,42 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Margin = new MarginPadding(2), Children = new Drawable[] { - judgementsContainer = new Container + new Container { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Width = judgement_line_width, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = chevron_size, RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.5f, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(chevron_size), + } }, colourBars = new Container { Width = bar_width, RelativeSizeAxes = Axes.Y, - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Children = new Drawable[] { colourBarsEarly = new Container { - Anchor = Anchor.y1 | alignment, - Origin = alignment, + Anchor = Anchor.CentreLeft, + Origin = Anchor.x2, RelativeSizeAxes = Axes.Both, Height = 0.5f, Scale = new Vector2(1, -1), }, colourBarsLate = new Container { - Anchor = Anchor.y1 | alignment, - Origin = alignment, + Anchor = Anchor.CentreLeft, + Origin = Anchor.x2, RelativeSizeAxes = Axes.Both, Height = 0.5f, }, @@ -115,21 +119,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } } }, - new Container + judgementsContainer = new Container { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Width = chevron_size, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = judgement_line_width, RelativeSizeAxes = Axes.Y, - Child = arrow = new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.5f, - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(chevron_size), - } }, } }; @@ -167,7 +162,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters // a little nub to mark the centre point. var centre = createColourBar(windows.Last().result, 0.01f); - centre.Anchor = centre.Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2); + centre.Anchor = centre.Origin = Anchor.CentreLeft; centre.Width = 2.5f; colourBars.Add(centre); @@ -239,8 +234,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, - Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2), + Origin = Anchor.CentreLeft, }); arrow.MoveToY( From c885ad87d5884105319658e0e2c476a45e759d3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 15:12:29 +0900 Subject: [PATCH 163/304] Update `HitErrorDisplay` tests --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 71 +++++++++++++++---- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 6cefd01aab..2c5443fe08 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -11,29 +14,35 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneHitErrorMeter : OsuTestScene { - private HitWindows hitWindows; - [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(); + [Cached(typeof(DrawableRuleset))] + private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset(); + public TestSceneHitErrorMeter() { recreateDisplay(new OsuHitWindows(), 5); AddRepeatStep("New random judgement", () => newJudgement(), 40); - AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20); - AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20); + AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); + AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); AddStep("Judgement barrage", () => @@ -83,10 +92,10 @@ namespace osu.Game.Tests.Visual.Gameplay private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { - this.hitWindows = hitWindows; - hitWindows?.SetDifficulty(overallDifficulty); + drawableRuleset.HitWindows = hitWindows; + Clear(); Add(new FillFlowContainer @@ -103,40 +112,40 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(new BarHitErrorMeter(hitWindows, true) + Add(new BarHitErrorMeter { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }); - Add(new BarHitErrorMeter(hitWindows, false) + Add(new BarHitErrorMeter { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }); - Add(new BarHitErrorMeter(hitWindows, true) + Add(new BarHitErrorMeter { Anchor = Anchor.BottomCentre, Origin = Anchor.CentreLeft, Rotation = 270, }); - Add(new ColourHitErrorMeter(hitWindows) + Add(new ColourHitErrorMeter { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding { Right = 50 } }); - Add(new ColourHitErrorMeter(hitWindows) + Add(new ColourHitErrorMeter { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Left = 50 } }); - Add(new ColourHitErrorMeter(hitWindows) + Add(new ColourHitErrorMeter { Anchor = Anchor.BottomCentre, Origin = Anchor.CentreLeft, @@ -147,11 +156,47 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(double offset = 0) { - scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) + scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, }); } + + [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] + private class TestDrawableRuleset : DrawableRuleset + { + public HitWindows HitWindows; + + public override IEnumerable Objects => new[] { new HitCircle { HitWindows = HitWindows } }; + + public override event Action NewResult; + public override event Action RevertResult; + + public override Playfield Playfield { get; } + public override Container Overlays { get; } + public override Container FrameStableComponents { get; } + public override IFrameStableClock FrameStableClock { get; } + public override IReadOnlyList Mods { get; } + + public override double GameplayStartTime { get; } + public override GameplayCursorContainer Cursor { get; } + + public TestDrawableRuleset() + : base(new OsuRuleset()) + { + // won't compile without this. + NewResult?.Invoke(null); + RevertResult?.Invoke(null); + } + + public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); + + public override void SetRecordTarget(Score score) => throw new NotImplementedException(); + + public override void RequestResume(Action continueResume) => throw new NotImplementedException(); + + public override void CancelResume() => throw new NotImplementedException(); + } } } From 5acb708939776029f96e6ec7bca92cec86ca3a40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 15:50:14 +0900 Subject: [PATCH 164/304] Remove customisation of hit error via standard settings --- osu.Game/Configuration/OsuConfigManager.cs | 2 - osu.Game/Configuration/ScoreMeterType.cs | 37 ------------------- .../Sections/Gameplay/GeneralSettings.cs | 5 --- 3 files changed, 44 deletions(-) delete mode 100644 osu.Game/Configuration/ScoreMeterType.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 09412b1f1b..43bbd725c3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -104,7 +104,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.KeyOverlay, false); SetDefault(OsuSetting.PositionalHitSounds, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); - SetDefault(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); SetDefault(OsuSetting.FloatingComments, false); @@ -213,7 +212,6 @@ namespace osu.Game.Configuration KeyOverlay, PositionalHitSounds, AlwaysPlayFirstComboBreak, - ScoreMeter, FloatingComments, HUDVisibilityMode, ShowProgressGraph, diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs deleted file mode 100644 index ddbd2327c2..0000000000 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ /dev/null @@ -1,37 +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.ComponentModel; - -namespace osu.Game.Configuration -{ - public enum ScoreMeterType - { - [Description("None")] - None, - - [Description("Hit Error (left)")] - HitErrorLeft, - - [Description("Hit Error (right)")] - HitErrorRight, - - [Description("Hit Error (left+right)")] - HitErrorBoth, - - [Description("Hit Error (bottom)")] - HitErrorBottom, - - [Description("Colour (left)")] - ColourLeft, - - [Description("Colour (right)")] - ColourRight, - - [Description("Colour (left+right)")] - ColourBoth, - - [Description("Colour (bottom)")] - ColourBottom, - } -} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index be464fa2b7..0b5ec4f338 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -73,11 +73,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Always play first combo break sound", Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak) }, - new SettingsEnumDropdown - { - LabelText = "Score meter type", - Current = config.GetBindable(OsuSetting.ScoreMeter) - }, new SettingsEnumDropdown { LabelText = "Score display mode", From 10c730b37d569e13f0dc9c522b57911b6671d7b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 15:50:40 +0900 Subject: [PATCH 165/304] Add new default locations for hit bar error displays --- osu.Game/Screens/Play/SongProgress.cs | 10 +++++++--- osu.Game/Skinning/DefaultSkin.cs | 26 ++++++++++++++++++++++++++ osu.Game/Skinning/HUDSkinComponents.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 16 ++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index b7939b5e75..cab44c7473 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -20,10 +20,14 @@ namespace osu.Game.Screens.Play { public class SongProgress : OverlayContainer, ISkinnableDrawable { - private const int info_height = 20; - private const int bottom_bar_height = 5; + public const float MAX_HEIGHT = info_height + bottom_bar_height + graph_height + handle_height; + + private const float info_height = 20; + private const float bottom_bar_height = 5; private const float graph_height = SquareGraph.Column.WIDTH * 6; - private static readonly Vector2 handle_size = new Vector2(10, 18); + private const float handle_height = 18; + + private static readonly Vector2 handle_size = new Vector2(10, handle_height); private const float transition_duration = 200; diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index d13ddcf22b..84f40df0cf 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -14,6 +14,7 @@ using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; using osuTK.Graphics; @@ -78,6 +79,23 @@ namespace osu.Game.Skinning combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); combo.Anchor = Anchor.TopCentre; } + + var hitError = container.OfType().FirstOrDefault(); + + if (hitError != null) + { + hitError.Anchor = Anchor.CentreLeft; + hitError.Origin = Anchor.CentreLeft; + } + + var hitError2 = container.OfType().LastOrDefault(); + + if (hitError2 != null) + { + hitError2.Anchor = Anchor.CentreRight; + hitError2.Origin = Anchor.CentreLeft; + hitError2.Scale = new Vector2(-1, 1); + } } }) { @@ -88,6 +106,8 @@ namespace osu.Game.Skinning GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)), } }; @@ -114,6 +134,12 @@ namespace osu.Game.Skinning case HUDSkinComponents.SongProgress: return new SongProgress(); + + case HUDSkinComponents.BarHitErrorMeter: + return new BarHitErrorMeter(); + + case HUDSkinComponents.ColourHitErrorMeter: + return new ColourHitErrorMeter(); } break; diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 2e6c3a9937..ea39c98635 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -10,5 +10,7 @@ namespace osu.Game.Skinning AccuracyCounter, HealthDisplay, SongProgress, + BarHitErrorMeter, + ColourHitErrorMeter, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 6c8d6ee45a..7a64f38840 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,6 +19,7 @@ using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK.Graphics; namespace osu.Game.Skinning @@ -342,6 +343,20 @@ namespace osu.Game.Skinning { accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; } + + var songProgress = container.OfType().FirstOrDefault(); + + var hitError = container.OfType().FirstOrDefault(); + + if (hitError != null) + { + hitError.Anchor = Anchor.BottomCentre; + hitError.Origin = Anchor.CentreLeft; + hitError.Rotation = -90; + + if (songProgress != null) + hitError.Y -= SongProgress.MAX_HEIGHT; + } }) { Children = new[] @@ -352,6 +367,7 @@ namespace osu.Game.Skinning GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)) ?? new BarHitErrorMeter(), } }; From ed957df162bbbfafdecaacab836ba6394c94e3a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 16:40:56 +0900 Subject: [PATCH 166/304] Add simple xmldoc to `TransferBlueprintFor` method --- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 6825e0f6a0..c62ea43331 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -84,6 +84,11 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(item); } + /// + /// Invoked when a has been transferred to another . + /// + /// The hit object which has been assigned to a new drawable. + /// The new drawable that is representing the hit object. protected virtual void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) { } From 61a41d97a4a61df9e95f47489b2ba69b23891f2d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 17:39:45 +0900 Subject: [PATCH 167/304] Add some xmldocs + comments --- .../Compose/HitObjectContainerEventQueue.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs index 7c21573b18..363f08cb41 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs @@ -68,6 +68,9 @@ namespace osu.Game.Screens.Edit.Compose switch (existingEvent, newEvent) { + // This mostly exists as a safeguard to ensure that the sequence: Began -> { Finished -> Began } -> Finished, where { ... } indicates a transferral within a single frame, + // correctly leads into a final "Finished" state. It's unlikely for this to happen normally as it requires the hitobject usage to finish (for the final time) + // immediately after the HitObjectContainer updates lifetime, but it's not inconceivable to occur with the Editor's scheduling and execution order. case (EventType.Transferred, EventType.Finished): pendingEvents[hitObject] = EventType.Finished; break; @@ -117,8 +120,24 @@ namespace osu.Game.Screens.Edit.Compose private enum EventType { + /// + /// A has started being used by a . + /// Began, + + /// + /// A has finished being used by a . + /// Finished, + + /// + /// An internal intermediate state that occurs when a has finished being used by one + /// and started being used by another in the same frame. The may be the same instance in both cases. + /// + /// + /// This usually occurs when a is transferred between s, + /// but also occurs if the dies and becomes alive again in the same frame within the same . + /// Transferred } } From c80e736712f396946fc755b700f89b3c72cf9863 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 18:31:57 +0900 Subject: [PATCH 168/304] Change `SkinBlueprint` to use the origin point as the selection point Not sure how this feels, but it makes using the same point throughout the editor possible, which I think is the correct way forward for now. --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 37659093e5..0a4bd1d75f 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Editor public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); - public override Vector2 ScreenSpaceSelectionPoint => drawable.ScreenSpaceDrawQuad.Centre; + public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition); public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; } From d661e98fa612e1297c8994418d9f82350edd1ed0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 18:34:06 +0900 Subject: [PATCH 169/304] Move common functionality out of `OsuSelectionHandler` and implement flip support --- .../Edit/OsuSelectionHandler.cs | 68 ++++--------------- .../Compose/Components/SelectionHandler.cs | 50 ++++++++++++++ .../Skinning/Editor/SkinSelectionHandler.cs | 12 +++- 3 files changed, 74 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index c2c1f6d602..aaf3517c9c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -12,12 +12,23 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; -using osuTK; +using Vector2 = osuTK.Vector2; namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : EditorSelectionHandler { + /// + /// During a transform, the initial origin is stored so it can be used throughout the operation. + /// + private Vector2? referenceOrigin; + + /// + /// During a transform, the initial path types of a single selected slider are stored so they + /// can be maintained throughout the operation. + /// + private List referencePathTypes; + protected override void OnSelectionChanged() { base.OnSelectionChanged(); @@ -50,17 +61,6 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } - /// - /// During a transform, the initial origin is stored so it can be used throughout the operation. - /// - private Vector2? referenceOrigin; - - /// - /// During a transform, the initial path types of a single selected slider are stored so they - /// can be maintained throughout the operation. - /// - private List referencePathTypes; - public override bool HandleReverse() { var hitObjects = EditorBeatmap.SelectedHitObjects; @@ -114,24 +114,10 @@ namespace osu.Game.Rulesets.Osu.Edit var hitObjects = selectedMovableObjects; var selectedObjectsQuad = getSurroundingQuad(hitObjects); - var centre = selectedObjectsQuad.Centre; foreach (var h in hitObjects) { - var pos = h.Position; - - switch (direction) - { - case Direction.Horizontal: - pos.X = centre.X - (pos.X - centre.X); - break; - - case Direction.Vertical: - pos.Y = centre.Y - (pos.Y - centre.Y); - break; - } - - h.Position = pos; + h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position); if (h is Slider slider) { @@ -204,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit { referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList(); - Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; @@ -333,7 +319,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// The hit objects to calculate a quad for. private Quad getSurroundingQuad(OsuHitObject[] hitObjects) => - getSurroundingQuad(hitObjects.SelectMany(h => + GetSurroundingQuad(hitObjects.SelectMany(h => { if (h is IHasPath path) { @@ -348,30 +334,6 @@ namespace osu.Game.Rulesets.Osu.Edit return new[] { h.Position }; })); - /// - /// Returns a gamefield-space quad surrounding the provided points. - /// - /// The points to calculate a quad for. - private Quad getSurroundingQuad(IEnumerable points) - { - if (!EditorBeatmap.SelectedHitObjects.Any()) - return new Quad(); - - Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); - Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); - - // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted - foreach (var p in points) - { - minPosition = Vector2.ComponentMin(minPosition, p); - maxPosition = Vector2.ComponentMax(maxPosition, p); - } - - Vector2 size = maxPosition - minPosition; - - return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); - } - /// /// All osu! hitobjects which can be moved/rotated/scaled. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6d8ae69812..bfd5ab7afa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -350,5 +350,55 @@ namespace osu.Game.Screens.Edit.Compose.Components => Enumerable.Empty(); #endregion + + #region Helper Methods + + /// + /// Given a flip direction, a surrounding quad for all selected objects, and a position, + /// will return the flipped position in screen space coordinates. + /// + protected static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position) + { + var centre = quad.Centre; + + switch (direction) + { + case Direction.Horizontal: + position.X = centre.X - (position.X - centre.X); + break; + + case Direction.Vertical: + position.Y = centre.Y - (position.Y - centre.Y); + break; + } + + return position; + } + + /// + /// Returns a quad surrounding the provided points. + /// + /// The points to calculate a quad for. + protected static Quad GetSurroundingQuad(IEnumerable points) + { + if (!points.Any()) + return new Quad(); + + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + + // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted + foreach (var p in points) + { + minPosition = Vector2.ComponentMin(minPosition, p); + maxPosition = Vector2.ComponentMax(maxPosition, p); + } + + Vector2 size = maxPosition - minPosition; + + return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); + } + + #endregion } } diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 7931a5ec41..2eb4ea107d 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -43,10 +43,16 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction) { - // TODO: this is temporary as well. - foreach (var c in SelectedBlueprints) + var selectionQuad = GetSurroundingQuad(SelectedBlueprints.Select(b => b.ScreenSpaceSelectionPoint)); + + foreach (var b in SelectedBlueprints) { - ((Drawable)c.Item).Scale *= new Vector2( + var drawableItem = (Drawable)b.Item; + + drawableItem.Position = + drawableItem.Parent.ToLocalSpace(GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint)) - drawableItem.AnchorPosition; + + drawableItem.Scale *= new Vector2( direction == Direction.Horizontal ? -1 : 1, direction == Direction.Vertical ? -1 : 1 ); From a31a6947bb4771472686cfd195d69747d1106c0d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 18:49:05 +0900 Subject: [PATCH 170/304] Add test --- .../TestSceneHitObjectContainerEventQueue.cs | 168 ++++++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 7 +- 2 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs new file mode 100644 index 0000000000..3f4d1b835f --- /dev/null +++ b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs @@ -0,0 +1,168 @@ +// 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.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Editing +{ + public class TestSceneHitObjectContainerEventQueue : OsuTestScene + { + private readonly TestHitObject testObj = new TestHitObject(); + + private TestPlayfield playfield1; + private TestPlayfield playfield2; + private TestDrawable intermediateDrawable; + private HitObjectContainerEventQueue eventQueue; + + private HitObject beganUsage; + private HitObject finishedUsage; + private HitObject transferredUsage; + + [SetUp] + public void Setup() => Schedule(() => + { + reset(); + + if (eventQueue != null) + { + eventQueue.HitObjectUsageBegan -= onHitObjectUsageBegan; + eventQueue.HitObjectUsageFinished -= onHitObjectUsageFinished; + eventQueue.HitObjectUsageTransferred -= onHitObjectUsageTransferred; + } + + var topPlayfield = new TestPlayfield(); + topPlayfield.AddNested(playfield1 = new TestPlayfield()); + topPlayfield.AddNested(playfield2 = new TestPlayfield()); + + eventQueue = new HitObjectContainerEventQueue(topPlayfield); + eventQueue.HitObjectUsageBegan += onHitObjectUsageBegan; + eventQueue.HitObjectUsageFinished += onHitObjectUsageFinished; + eventQueue.HitObjectUsageTransferred += onHitObjectUsageTransferred; + + Children = new Drawable[] + { + topPlayfield, + intermediateDrawable = new TestDrawable(), + eventQueue + }; + }); + + private void onHitObjectUsageBegan(HitObject obj) => beganUsage = obj; + + private void onHitObjectUsageFinished(HitObject obj) => finishedUsage = obj; + + private void onHitObjectUsageTransferred(HitObject obj, DrawableHitObject drawableObj) => transferredUsage = obj; + + [Test] + public void TestUsageBeganAfterAdd() + { + AddStep("add hitobject", () => playfield1.Add(testObj)); + addCheckStep(began: true); + } + + [Test] + public void TestUsageFinishedAfterRemove() + { + AddStep("add hitobject", () => playfield1.Add(testObj)); + addResetStep(); + AddStep("remove hitobject", () => playfield1.Remove(testObj)); + addCheckStep(finished: true); + } + + [Test] + public void TestUsageTransferredWhenMovedBetweenPlayfields() + { + AddStep("add hitobject", () => playfield1.Add(testObj)); + addResetStep(); + AddStep("transfer hitobject to other playfield", () => + { + playfield1.Remove(testObj); + playfield2.Add(testObj); + }); + + addCheckStep(transferred: true); + } + + [Test] + public void TestRemoveImmediatelyAfterUsageBegan() + { + AddStep("add hitobject and schedule removal", () => + { + playfield1.Add(testObj); + intermediateDrawable.Schedule(() => playfield1.Remove(testObj)); + }); + + addCheckStep(); + } + + [Test] + public void TestRemoveImmediatelyAfterTransferred() + { + AddStep("add hitobject", () => playfield1.Add(testObj)); + addResetStep(); + AddStep("transfer hitobject to other playfield and schedule removal", () => + { + playfield1.Remove(testObj); + playfield2.Add(testObj); + intermediateDrawable.Schedule(() => playfield2.Remove(testObj)); + }); + + addCheckStep(finished: true); + } + + private void addResetStep() => AddStep("reset", reset); + + private void reset() + { + beganUsage = null; + finishedUsage = null; + transferredUsage = null; + } + + private void addCheckStep(bool began = false, bool finished = false, bool transferred = false) + => AddAssert($"began = {began}, finished = {finished}, transferred = {transferred}", + () => (beganUsage == testObj) == began && (finishedUsage == testObj) == finished && (transferredUsage == testObj) == transferred); + + private class TestPlayfield : Playfield + { + public TestPlayfield() + { + RegisterPool(1); + } + + public new void AddNested(Playfield playfield) + { + AddInternal(playfield); + base.AddNested(playfield); + } + + protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) + { + var entry = base.CreateLifetimeEntry(hitObject); + entry.KeepAlive = true; + return entry; + } + } + + private class TestHitObject : HitObject + { + public override string ToString() => "TestHitObject"; + } + + private class TestDrawableHitObject : DrawableHitObject + { + } + + private class TestDrawable : Drawable + { + public new void Schedule(Action action) => base.Schedule(action); + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 17d3cf01a4..b154288dba 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -354,8 +354,11 @@ namespace osu.Game.Rulesets.UI // If this is the first time this DHO is being used, then apply the DHO mods. // This is done before Apply() so that the state is updated once when the hitobject is applied. - foreach (var m in mods.OfType()) - m.ApplyToDrawableHitObjects(dho.Yield()); + if (mods != null) + { + foreach (var m in mods.OfType()) + m.ApplyToDrawableHitObjects(dho.Yield()); + } } if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) From bfc0205e9bf378039a87d6d67c05102a7aa4fed3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 18:49:11 +0900 Subject: [PATCH 171/304] Fix (began, finished) event --- .../Edit/Compose/HitObjectContainerEventQueue.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs index 363f08cb41..6c1a3b06bb 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs @@ -68,14 +68,20 @@ namespace osu.Game.Screens.Edit.Compose switch (existingEvent, newEvent) { - // This mostly exists as a safeguard to ensure that the sequence: Began -> { Finished -> Began } -> Finished, where { ... } indicates a transferral within a single frame, - // correctly leads into a final "Finished" state. It's unlikely for this to happen normally as it requires the hitobject usage to finish (for the final time) - // immediately after the HitObjectContainer updates lifetime, but it's not inconceivable to occur with the Editor's scheduling and execution order. + // This exists as a safeguard to ensure that the sequence: { Began -> Finished }, where { ... } indicates a sequence within a single frame, does not trigger any events. + // This is unlikely to occur in practice as it requires the usage to finish immediately after the HitObjectContainer updates hitobject lifetimes, + // however, an Editor action scheduled somewhere between the lifetime update and this event queue's own Update() could cause this. + case (EventType.Began, EventType.Finished): + pendingEvents.Remove(hitObject); + break; + + // This exists as a safeguard to ensure that the sequence: Began -> { Finished -> Began -> Finished }, where { ... } indicates a sequence within a single frame, + // correctly leads into a final "finished" state rather than remaining in the intermediate "transferred" state. + // As above, this is unlikely to occur in practice. case (EventType.Transferred, EventType.Finished): pendingEvents[hitObject] = EventType.Finished; break; - case (EventType.Began, EventType.Finished): case (EventType.Finished, EventType.Began): pendingEvents[hitObject] = EventType.Transferred; break; From 633f841a0f691ecb89965cc4762c510e7f3cd4b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 18:57:02 +0900 Subject: [PATCH 172/304] Rename to HitObjectUsageEventBuffer --- .../TestSceneHitObjectContainerEventQueue.cs | 20 +++++++++---------- .../Components/EditorBlueprintContainer.cs | 2 +- ...tQueue.cs => HitObjectUsageEventBuffer.cs} | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) rename osu.Game/Screens/Edit/Compose/{HitObjectContainerEventQueue.cs => HitObjectUsageEventBuffer.cs} (93%) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs index 3f4d1b835f..ebf98c4c56 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Editing private TestPlayfield playfield1; private TestPlayfield playfield2; private TestDrawable intermediateDrawable; - private HitObjectContainerEventQueue eventQueue; + private HitObjectUsageEventBuffer eventBuffer; private HitObject beganUsage; private HitObject finishedUsage; @@ -30,27 +30,27 @@ namespace osu.Game.Tests.Editing { reset(); - if (eventQueue != null) + if (eventBuffer != null) { - eventQueue.HitObjectUsageBegan -= onHitObjectUsageBegan; - eventQueue.HitObjectUsageFinished -= onHitObjectUsageFinished; - eventQueue.HitObjectUsageTransferred -= onHitObjectUsageTransferred; + eventBuffer.HitObjectUsageBegan -= onHitObjectUsageBegan; + eventBuffer.HitObjectUsageFinished -= onHitObjectUsageFinished; + eventBuffer.HitObjectUsageTransferred -= onHitObjectUsageTransferred; } var topPlayfield = new TestPlayfield(); topPlayfield.AddNested(playfield1 = new TestPlayfield()); topPlayfield.AddNested(playfield2 = new TestPlayfield()); - eventQueue = new HitObjectContainerEventQueue(topPlayfield); - eventQueue.HitObjectUsageBegan += onHitObjectUsageBegan; - eventQueue.HitObjectUsageFinished += onHitObjectUsageFinished; - eventQueue.HitObjectUsageTransferred += onHitObjectUsageTransferred; + eventBuffer = new HitObjectUsageEventBuffer(topPlayfield); + eventBuffer.HitObjectUsageBegan += onHitObjectUsageBegan; + eventBuffer.HitObjectUsageFinished += onHitObjectUsageFinished; + eventBuffer.HitObjectUsageTransferred += onHitObjectUsageTransferred; Children = new Drawable[] { topPlayfield, intermediateDrawable = new TestDrawable(), - eventQueue + eventBuffer }; }); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index c62ea43331..fdea26d92b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var obj in Composer.HitObjects) AddBlueprintFor(obj.HitObject); - var eventQueue = new HitObjectContainerEventQueue(Composer.Playfield); + var eventQueue = new HitObjectUsageEventBuffer(Composer.Playfield); eventQueue.HitObjectUsageBegan += AddBlueprintFor; eventQueue.HitObjectUsageFinished += RemoveBlueprintFor; eventQueue.HitObjectUsageTransferred += TransferBlueprintFor; diff --git a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs similarity index 93% rename from osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs rename to osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs index 6c1a3b06bb..8c69e9e707 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectContainerEventQueue.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs @@ -13,9 +13,9 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Edit.Compose { /// - /// A queue which processes events from the many s in a nested hierarchy. + /// Buffers events from the many s in a nested hierarchy. /// - internal class HitObjectContainerEventQueue : Component + internal class HitObjectUsageEventBuffer : Component { /// /// Invoked when a becomes used by a . @@ -41,10 +41,10 @@ namespace osu.Game.Screens.Edit.Compose private readonly Playfield playfield; /// - /// Creates a new . + /// Creates a new . /// /// The most top-level . - public HitObjectContainerEventQueue([NotNull] Playfield playfield) + public HitObjectUsageEventBuffer([NotNull] Playfield playfield) { this.playfield = playfield; @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Edit.Compose { // This exists as a safeguard to ensure that the sequence: { Began -> Finished }, where { ... } indicates a sequence within a single frame, does not trigger any events. // This is unlikely to occur in practice as it requires the usage to finish immediately after the HitObjectContainer updates hitobject lifetimes, - // however, an Editor action scheduled somewhere between the lifetime update and this event queue's own Update() could cause this. + // however, an Editor action scheduled somewhere between the lifetime update and this buffer's own Update() could cause this. case (EventType.Began, EventType.Finished): pendingEvents.Remove(hitObject); break; From 97f4f7bbd1473b7664ab9d07cc8a38382b72391a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 18:59:45 +0900 Subject: [PATCH 173/304] Remove Component inheritance --- ...TestSceneHitObjectContainerEventBuffer.cs} | 9 +++++++-- .../Components/EditorBlueprintContainer.cs | 19 ++++++++++++++----- .../Edit/Compose/HitObjectUsageEventBuffer.cs | 18 ++++++++---------- 3 files changed, 29 insertions(+), 17 deletions(-) rename osu.Game.Tests/Editing/{TestSceneHitObjectContainerEventQueue.cs => TestSceneHitObjectContainerEventBuffer.cs} (96%) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs similarity index 96% rename from osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs rename to osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs index ebf98c4c56..30e72150f1 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventQueue.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Editing { - public class TestSceneHitObjectContainerEventQueue : OsuTestScene + public class TestSceneHitObjectContainerEventBuffer : OsuTestScene { private readonly TestHitObject testObj = new TestHitObject(); @@ -50,7 +50,6 @@ namespace osu.Game.Tests.Editing { topPlayfield, intermediateDrawable = new TestDrawable(), - eventBuffer }; }); @@ -117,6 +116,12 @@ namespace osu.Game.Tests.Editing addCheckStep(finished: true); } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + eventBuffer.Update(); + } + private void addResetStep() => AddStep("reset", reset); private void reset() diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index fdea26d92b..5a6f98f504 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly HitObjectComposer Composer; + private HitObjectUsageEventBuffer usageEventBuffer; + protected EditorBlueprintContainer(HitObjectComposer composer) { Composer = composer; @@ -46,14 +48,19 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var obj in Composer.HitObjects) AddBlueprintFor(obj.HitObject); - var eventQueue = new HitObjectUsageEventBuffer(Composer.Playfield); - eventQueue.HitObjectUsageBegan += AddBlueprintFor; - eventQueue.HitObjectUsageFinished += RemoveBlueprintFor; - eventQueue.HitObjectUsageTransferred += TransferBlueprintFor; - AddInternal(eventQueue); + usageEventBuffer = new HitObjectUsageEventBuffer(Composer.Playfield); + usageEventBuffer.HitObjectUsageBegan += AddBlueprintFor; + usageEventBuffer.HitObjectUsageFinished += RemoveBlueprintFor; + usageEventBuffer.HitObjectUsageTransferred += TransferBlueprintFor; } } + protected override void Update() + { + base.Update(); + usageEventBuffer?.Update(); + } + protected override IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints.OrderBy(b => b.Item.StartTime); @@ -145,6 +152,8 @@ namespace osu.Game.Screens.Edit.Compose.Components Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; } + + usageEventBuffer?.Dispose(); } } } diff --git a/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs index 8c69e9e707..cbaf9b4f26 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Compose /// /// Buffers events from the many s in a nested hierarchy. /// - internal class HitObjectUsageEventBuffer : Component + internal class HitObjectUsageEventBuffer : IDisposable { /// /// Invoked when a becomes used by a . @@ -91,10 +90,8 @@ namespace osu.Game.Screens.Edit.Compose } } - protected override void Update() + public void Update() { - base.Update(); - foreach (var (hitObject, e) in pendingEvents) { switch (e) @@ -116,12 +113,13 @@ namespace osu.Game.Screens.Edit.Compose pendingEvents.Clear(); } - protected override void Dispose(bool isDisposing) + public void Dispose() { - base.Dispose(isDisposing); - - playfield.HitObjectUsageBegan -= onHitObjectUsageBegan; - playfield.HitObjectUsageFinished -= onHitObjectUsageFinished; + if (playfield != null) + { + playfield.HitObjectUsageBegan -= onHitObjectUsageBegan; + playfield.HitObjectUsageFinished -= onHitObjectUsageFinished; + } } private enum EventType From ab6a79f84cccfe9b72f7dcb9966010366f4b0cda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 19:10:45 +0900 Subject: [PATCH 174/304] Simplify --- .../TestSceneHitObjectContainerEventBuffer.cs | 4 +- .../Edit/Compose/HitObjectUsageEventBuffer.cs | 88 +++---------------- 2 files changed, 13 insertions(+), 79 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs index 30e72150f1..5233cbc0be 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Editing intermediateDrawable.Schedule(() => playfield1.Remove(testObj)); }); - addCheckStep(); + addCheckStep(began: true, finished: true); } [Test] @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Editing intermediateDrawable.Schedule(() => playfield2.Remove(testObj)); }); - addCheckStep(finished: true); + addCheckStep(transferred: true, finished: true); } protected override void UpdateAfterChildren() diff --git a/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs index cbaf9b4f26..fce5aa42ac 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs @@ -51,66 +51,23 @@ namespace osu.Game.Screens.Edit.Compose playfield.HitObjectUsageFinished += onHitObjectUsageFinished; } - private readonly Dictionary pendingEvents = new Dictionary(); + private readonly List usageFinishedHitObjects = new List(); - private void onHitObjectUsageBegan(HitObject hitObject) => updateEvent(hitObject, EventType.Began); - - private void onHitObjectUsageFinished(HitObject hitObject) => updateEvent(hitObject, EventType.Finished); - - private void updateEvent(HitObject hitObject, EventType newEvent) + private void onHitObjectUsageBegan(HitObject hitObject) { - if (!pendingEvents.TryGetValue(hitObject, out EventType existingEvent)) - { - pendingEvents[hitObject] = newEvent; - return; - } - - switch (existingEvent, newEvent) - { - // This exists as a safeguard to ensure that the sequence: { Began -> Finished }, where { ... } indicates a sequence within a single frame, does not trigger any events. - // This is unlikely to occur in practice as it requires the usage to finish immediately after the HitObjectContainer updates hitobject lifetimes, - // however, an Editor action scheduled somewhere between the lifetime update and this buffer's own Update() could cause this. - case (EventType.Began, EventType.Finished): - pendingEvents.Remove(hitObject); - break; - - // This exists as a safeguard to ensure that the sequence: Began -> { Finished -> Began -> Finished }, where { ... } indicates a sequence within a single frame, - // correctly leads into a final "finished" state rather than remaining in the intermediate "transferred" state. - // As above, this is unlikely to occur in practice. - case (EventType.Transferred, EventType.Finished): - pendingEvents[hitObject] = EventType.Finished; - break; - - case (EventType.Finished, EventType.Began): - pendingEvents[hitObject] = EventType.Transferred; - break; - - default: - throw new ArgumentOutOfRangeException($"Unexpected event update ({existingEvent} => {newEvent})."); - } + if (usageFinishedHitObjects.Remove(hitObject)) + HitObjectUsageTransferred?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); + else + HitObjectUsageBegan?.Invoke(hitObject); } + private void onHitObjectUsageFinished(HitObject hitObject) => usageFinishedHitObjects.Add(hitObject); + public void Update() { - foreach (var (hitObject, e) in pendingEvents) - { - switch (e) - { - case EventType.Began: - HitObjectUsageBegan?.Invoke(hitObject); - break; - - case EventType.Transferred: - HitObjectUsageTransferred?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); - break; - - case EventType.Finished: - HitObjectUsageFinished?.Invoke(hitObject); - break; - } - } - - pendingEvents.Clear(); + foreach (var hitObject in usageFinishedHitObjects) + HitObjectUsageFinished?.Invoke(hitObject); + usageFinishedHitObjects.Clear(); } public void Dispose() @@ -121,28 +78,5 @@ namespace osu.Game.Screens.Edit.Compose playfield.HitObjectUsageFinished -= onHitObjectUsageFinished; } } - - private enum EventType - { - /// - /// A has started being used by a . - /// - Began, - - /// - /// A has finished being used by a . - /// - Finished, - - /// - /// An internal intermediate state that occurs when a has finished being used by one - /// and started being used by another in the same frame. The may be the same instance in both cases. - /// - /// - /// This usually occurs when a is transferred between s, - /// but also occurs if the dies and becomes alive again in the same frame within the same . - /// - Transferred - } } } From d93ac7ac9842d08a0f0995779e1c179ed031870e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 19:13:13 +0900 Subject: [PATCH 175/304] Change class xmldoc a bit --- osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs index fce5aa42ac..621c901fb9 100644 --- a/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs +++ b/osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs @@ -12,7 +12,8 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Edit.Compose { /// - /// Buffers events from the many s in a nested hierarchy. + /// Buffers events from the many s in a nested hierarchy + /// to ensure correct ordering of events. /// internal class HitObjectUsageEventBuffer : IDisposable { From 2c65b8fa9366fded6ae5f3f2167b768a3017f3d5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 18 May 2021 19:55:25 +0900 Subject: [PATCH 176/304] Revert "Fix uninitialized scrollLength value is used" This reverts commit 73dfb04d --- .../Scrolling/ScrollingHitObjectContainer.cs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 538d4d1d11..915bab9a51 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -185,6 +185,8 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Remove(hitObject); } + private float scrollLength; + protected override void Update() { base.Update(); @@ -197,16 +199,29 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Clear(); scrollingInfo.Algorithm.Reset(); + + switch (direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + scrollLength = DrawSize.Y; + break; + + default: + scrollLength = DrawSize.X; + break; + } + layoutCache.Validate(); } } - // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes - // to prevent hit objects displayed in a wrong position for one frame. protected override void UpdateAfterChildrenLife() { base.UpdateAfterChildrenLife(); + // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes + // to prevent hit objects displayed in a wrong position for one frame. // Only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes). foreach (var obj in AliveObjects) { @@ -245,7 +260,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, getLength()); + entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); } private void updateLayoutRecursive(DrawableHitObject hitObject) @@ -256,12 +271,12 @@ namespace osu.Game.Rulesets.UI.Scrolling { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; } } @@ -280,19 +295,19 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (direction.Value) { case ScrollingDirection.Up: - hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Down: - hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Left: - hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Right: - hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; } } From 84a1a86c6329ff60021190f4c070c28707e736c3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 18 May 2021 19:55:31 +0900 Subject: [PATCH 177/304] Revert "Use entry to calculate lifetime in ScrollingHOC" This reverts commit 632bb70e --- .../Scrolling/ScrollingHitObjectContainer.cs | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 915bab9a51..a9eaf3da68 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -5,9 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Primitives; using osu.Framework.Layout; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -19,18 +17,16 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); + /// + /// Hit objects which require lifetime computation in the next update call. + /// + private readonly HashSet toComputeLifetime = new HashSet(); + /// /// A set containing all which have an up-to-date layout. /// private readonly HashSet layoutComputed = new HashSet(); - /// - /// A conservative estimate of maximum bounding box of a - /// with respect to the start time position of the hit object. - /// It is used to calculate when the object appears inbound. - /// - protected virtual RectangleF GetDrawRectangle(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100); - [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -58,6 +54,7 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(); + toComputeLifetime.Clear(); layoutComputed.Clear(); } @@ -169,6 +166,7 @@ namespace osu.Game.Rulesets.UI.Scrolling private void onRemoveRecursive(DrawableHitObject hitObject) { + toComputeLifetime.Remove(hitObject); layoutComputed.Remove(hitObject); hitObject.DefaultsApplied -= invalidateHitObject; @@ -177,11 +175,14 @@ namespace osu.Game.Rulesets.UI.Scrolling onRemoveRecursive(nested); } + /// + /// Make this lifetime and layout computed in next update. + /// private void invalidateHitObject(DrawableHitObject hitObject) { - if (hitObject.ParentHitObject == null) - updateLifetime(hitObject.Entry); - + // Lifetime computation is delayed until next update because + // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. + toComputeLifetime.Add(hitObject); layoutComputed.Remove(hitObject); } @@ -193,8 +194,13 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - foreach (var entry in Entries) - updateLifetime(entry); + toComputeLifetime.Clear(); + + foreach (var hitObject in Objects) + { + if (hitObject.HitObject != null) + toComputeLifetime.Add(hitObject); + } layoutComputed.Clear(); @@ -214,6 +220,11 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutCache.Validate(); } + + foreach (var hitObject in toComputeLifetime) + hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); + + toComputeLifetime.Clear(); } protected override void UpdateAfterChildrenLife() @@ -236,31 +247,32 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private void updateLifetime(HitObjectLifetimeEntry entry) + private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) { - var rectangle = GetDrawRectangle(entry); - float startOffset = 0; + float originAdjustment = 0.0f; + // calculate the dimension of the part of the hitobject that should already be visible + // when the hitobject origin first appears inside the scrolling container switch (direction.Value) { - case ScrollingDirection.Right: - startOffset = rectangle.Right; + case ScrollingDirection.Up: + originAdjustment = hitObject.OriginPosition.Y; break; case ScrollingDirection.Down: - startOffset = rectangle.Bottom; + originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y; break; case ScrollingDirection.Left: - startOffset = -rectangle.Left; + originAdjustment = hitObject.OriginPosition.X; break; - case ScrollingDirection.Up: - startOffset = -rectangle.Top; + case ScrollingDirection.Right: + originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X; break; } - entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); + return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); } private void updateLayoutRecursive(DrawableHitObject hitObject) From ee9fe3c4bef77f8c7e4c3d67882fa741e1d86932 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 18 May 2021 19:55:44 +0900 Subject: [PATCH 178/304] Revert "Add failing test showing lifetime not recomputed with pooled objects" This reverts commit b88e5a31 --- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 75a5eec6f7..9931ee4a45 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -90,20 +90,6 @@ namespace osu.Game.Tests.Visual.Gameplay assertChildPosition(5); } - [TestCase("pooled")] - [TestCase("non-pooled")] - public void TestLifetimeRecomputedWhenTimeRangeChanges(string pooled) - { - var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject()); - beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); - createTest(beatmap); - - assertDead(3); - - AddStep("increase time range", () => drawableRuleset.TimeRange.Value = 3 * time_range); - assertPosition(3, 1); - } - [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { From 5f94b3bdacfcd6c7f4eb53e038767df905d234d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 21:03:59 +0900 Subject: [PATCH 179/304] Remove legacy playlist item ID handling --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index c0706b082d..7fe48d54b1 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -92,10 +92,6 @@ namespace osu.Game.Online.Multiplayer [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; - // Only exists for compatibility with old osu-server-spectator build. - // Todo: Can be removed on 2021/02/26. - private long defaultPlaylistItemId; - private Room? apiRoom; [BackgroundDependencyLoader] @@ -143,7 +139,6 @@ namespace osu.Game.Online.Multiplayer { Room = joinedRoom; apiRoom = room; - defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0; foreach (var user in joinedRoom.Users) updateUserPlayingState(user.UserID, user.State); }, cancellationSource.Token).ConfigureAwait(false); @@ -581,7 +576,7 @@ namespace osu.Game.Online.Multiplayer void updateItem(PlaylistItem item) { - item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : settings.PlaylistItemId; + item.ID = settings.PlaylistItemId; item.Beatmap.Value = beatmap; item.Ruleset.Value = ruleset.RulesetInfo; item.RequiredMods.Clear(); From c6160d53044b35abd0ee8168b0bb28344dd4d345 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 21:17:33 +0900 Subject: [PATCH 180/304] Only ignore online score id for database import --- osu.Game/Screens/Play/Player.cs | 17 ++++++++++++++--- osu.Game/Screens/Play/SubmittingPlayer.cs | 7 +------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 890883f0d2..6317a41bec 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -929,11 +929,11 @@ namespace osu.Game.Screens.Play /// /// The to import. /// The imported score. - protected virtual Task ImportScore(Score score) + protected virtual async Task ImportScore(Score score) { // Replays are already populated and present in the game's database, so should not be re-imported. if (DrawableRuleset.ReplayScore != null) - return Task.CompletedTask; + return; LegacyByteArrayReader replayReader; @@ -943,7 +943,18 @@ namespace osu.Game.Screens.Play replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); } - return scoreManager.Import(score.ScoreInfo, replayReader); + // For the time being, online ID responses are not really useful for anything. + // In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores. + // + // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint + // conflicts across various systems (ie. solo and multiplayer). + long? onlineScoreId = score.ScoreInfo.OnlineScoreID; + score.ScoreInfo.OnlineScoreID = null; + + await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); + + // ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen). + score.ScoreInfo.OnlineScoreID = onlineScoreId; } /// diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index d5ad87c70c..23b9037244 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -116,12 +116,7 @@ namespace osu.Game.Screens.Play request.Success += s => { - // For the time being, online ID responses are not really useful for anything. - // In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores. - // - // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint - // conflicts across various systems (ie. solo and multiplayer). - // score.ScoreInfo.OnlineScoreID = s.ID; + score.ScoreInfo.OnlineScoreID = s.ID; tcs.SetResult(true); }; From 775e0fbde5e6de0a7d20558e105b251ed994f8da Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 18 May 2021 15:27:20 +0200 Subject: [PATCH 181/304] Mark StableImportManager as nullable. --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 729e25203f..74e10037ab 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; - protected virtual bool DisplayStableImportPrompt => stableImportManager.SupportsImportFromStable; + protected virtual bool DisplayStableImportPrompt => stableImportManager?.SupportsImportFromStable == true; /// /// Can be null if is false. @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private StableImportManager stableImportManager { get; set; } protected ModSelectOverlay ModSelect { get; private set; } From 76a377f3e09d88bde52e9309558633219f118e3f Mon Sep 17 00:00:00 2001 From: Vinicius Barbosa Date: Tue, 18 May 2021 15:30:45 +0200 Subject: [PATCH 182/304] Fixed applause sound stopping after switching scores --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 10 ---------- osu.Game/Screens/Ranking/ResultsScreen.cs | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index bca3a07fa6..28829c4ed8 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -83,8 +83,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - private SkinnableSound applauseSound; - public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; @@ -211,13 +209,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy }, rankText = new RankText(score.Rank) }; - - if (withFlair) - { - AddInternal(applauseSound = score.Rank >= ScoreRank.A - ? new SkinnableSound(new SampleInfo("Results/rankpass", "applause")) - : new SkinnableSound(new SampleInfo("Results/rankfail"))); - } } private ScoreRank getRank(ScoreRank rank) @@ -256,7 +247,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true)) { - this.Delay(-1440).Schedule(() => applauseSound?.Play()); rankText.Appear(); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c1f5d92d17..20480c5367 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -19,7 +20,9 @@ using osu.Game.Online.API; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Ranking @@ -56,6 +59,8 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; private readonly bool allowWatchingReplay; + private SkinnableSound applauseSound; + protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) { Score = score; @@ -146,6 +151,13 @@ namespace osu.Game.Screens.Ranking bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay); ScorePanelList.AddScore(Score, shouldFlair); + + if (shouldFlair) + { + AddInternal(applauseSound = Score.Rank >= ScoreRank.A + ? new SkinnableSound(new SampleInfo("Results/rankpass", "applause")) + : new SkinnableSound(new SampleInfo("Results/rankfail"))); + } } if (allowWatchingReplay) @@ -183,6 +195,11 @@ namespace osu.Game.Screens.Ranking api.Queue(req); statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); + + using(BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY, true)) + { + this.Delay(-1000).Schedule(() => applauseSound?.Play()); + } } protected override void Update() From 06fffc499b3dd28759e44af39a5ca66176fbd193 Mon Sep 17 00:00:00 2001 From: Vinicius Barbosa Date: Tue, 18 May 2021 16:56:07 +0200 Subject: [PATCH 183/304] Removed unused variables and directives --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 5 ----- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 28829c4ed8..ec48a6313b 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -10,11 +10,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Ranking.Expanded.Accuracy @@ -76,8 +74,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private readonly ScoreInfo score; - private readonly bool withFlair; - private SmoothCircularProgress accuracyCircle; private SmoothCircularProgress innerMask; private Container badges; @@ -86,7 +82,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; - this.withFlair = withFlair; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 20480c5367..ab065f7dd4 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Ranking statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); - using(BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY, true)) + using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY, true)) { this.Delay(-1000).Schedule(() => applauseSound?.Play()); } From ac5fe0c18cf50c2fa8b1bd112d0f9499b0516f18 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 18 May 2021 15:58:18 +0200 Subject: [PATCH 184/304] Change larger freemod selection overlay --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 375aac729d..fa30826ee9 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Match Origin = Anchor.BottomLeft, Depth = float.MinValue, RelativeSizeAxes = Axes.Both, - Height = 0.5f, + Height = 1.0f, Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, Child = userModsSelectOverlay = new UserModSelectOverlay { From d05ffdf120c0f13080f43d71b16884efb9d5da8c Mon Sep 17 00:00:00 2001 From: Vinicius Barbosa Date: Tue, 18 May 2021 20:19:18 +0200 Subject: [PATCH 185/304] Added constants for delay value --- .../Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- .../Expanded/Accuracy/AccuracyCircle.cs | 7 ++++++- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ScorePanel.cs | 18 +++++++++--------- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 2af15923a0..1e87893f39 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking } } }, - new AccuracyCircle(score, true) + new AccuracyCircle(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index ec48a6313b..4fca4759f2 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -37,6 +37,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public const double ACCURACY_TRANSFORM_DURATION = 3000; + /// + /// Delay before the default applause sound is played to match the timing + /// + public const double APPLAUSE_DELAY = 1440; + /// /// Delay after for the rank text (A/B/C/D/S/SS) to appear. /// @@ -79,7 +84,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score, bool withFlair) + public AccuracyCircle(ScoreInfo score) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 6a6b39b61c..4895240314 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score, withFlair) + Child = new AccuracyCircle(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index ab065f7dd4..e25bbbe253 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -198,7 +198,7 @@ namespace osu.Game.Screens.Ranking using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY, true)) { - this.Delay(-1000).Schedule(() => applauseSound?.Play()); + this.Delay(ScorePanel.RESIZE_DURATION + ScorePanel.TOP_LAYER_EXPAND_DELAY - AccuracyCircle.APPLAUSE_DELAY).Schedule(() => applauseSound?.Play()); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index df710e4eb8..f66a998db6 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -54,12 +54,12 @@ namespace osu.Game.Screens.Ranking /// /// Duration for the panel to resize into its expanded/contracted size. /// - private const double resize_duration = 200; + public const double RESIZE_DURATION = 200; /// - /// Delay after before the top layer is expanded. + /// Delay after before the top layer is expanded. /// - private const double top_layer_expand_delay = 100; + public const double TOP_LAYER_EXPAND_DELAY = 100; /// /// Duration for the top layer expansion. @@ -208,8 +208,8 @@ namespace osu.Game.Screens.Ranking case PanelState.Expanded: Size = new Vector2(EXPANDED_WIDTH, expanded_height); - topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); - middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); + topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); + middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); @@ -221,20 +221,20 @@ namespace osu.Game.Screens.Ranking case PanelState.Contracted: Size = new Vector2(CONTRACTED_WIDTH, contracted_height); - topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); - middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); + topLayerBackground.FadeColour(contracted_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); + middleLayerBackground.FadeColour(contracted_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0)); middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; } - content.ResizeTo(Size, resize_duration, Easing.OutQuint); + content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. - using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true)) + using (BeginDelayedSequence(topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY, true)) { topLayerContainer.FadeIn(); From d70d37b7f46ec340113e0e531ac38f0975eccf63 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 18 May 2021 22:30:36 +0300 Subject: [PATCH 186/304] Remove convoluted autosize logic in MonthSection --- .../Overlays/News/Sidebar/MonthSection.cs | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 166da97f93..2fc88b3909 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -139,24 +139,23 @@ namespace osu.Game.Overlays.News.Sidebar { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + AutoSizeDuration = animation_duration; + AutoSizeEasing = Easing.Out; InternalChild = content = new FillFlowContainer { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5) + Spacing = new Vector2(0, 5), + Alpha = 0 }; } protected override void LoadComplete() { base.LoadComplete(); - IsOpen.BindValueChanged(_ => updateState(), true); - - // First state change should be instant. - FinishTransforms(true); } private void updateState() @@ -176,22 +175,6 @@ namespace osu.Game.Overlays.News.Sidebar content.FadeOut(animation_duration, Easing.OutQuint); } } - - private bool autoSizeTransitionApplied; - - // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutoSize{Duration,Easing}. - protected override void UpdateAfterAutoSize() - { - base.UpdateAfterAutoSize(); - - if (!autoSizeTransitionApplied) - { - AutoSizeDuration = animation_duration; - AutoSizeEasing = Easing.OutQuint; - - autoSizeTransitionApplied = true; - } - } } } } From 1fd00d1313bd7ff8c5acf828843170b5f287b283 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 18 May 2021 21:52:28 +0200 Subject: [PATCH 187/304] Change from fullscreen to 0.7 --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index fa30826ee9..7395b346a4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Match Origin = Anchor.BottomLeft, Depth = float.MinValue, RelativeSizeAxes = Axes.Both, - Height = 1.0f, + Height = 0.7f, Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, Child = userModsSelectOverlay = new UserModSelectOverlay { From bde7f88eb3bbc69e0528cab118b698fa08600825 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 18 May 2021 14:23:22 -0700 Subject: [PATCH 188/304] Don't show warning screen when registering on dev server --- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index b2096968fe..ea5f1a6278 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -26,11 +26,14 @@ namespace osu.Game.Overlays.AccountCreation [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } + [Resolved] + private OsuGameBase game { get; set; } + private const string help_centre_url = "/help/wiki/Help_Centre#login"; public override void OnEntering(IScreen last) { - if (string.IsNullOrEmpty(api?.ProvidedUsername)) + if (string.IsNullOrEmpty(api?.ProvidedUsername) || game.UseDevelopmentServer) { this.FadeOut(); this.Push(new ScreenEntry()); From 4cc573690e7d366cf801a235c156350fda7a0261 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 18 May 2021 14:32:56 -0700 Subject: [PATCH 189/304] Move OsuGame out of load method --- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index ea5f1a6278..2605a0a6d6 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -26,8 +26,8 @@ namespace osu.Game.Overlays.AccountCreation [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } - [Resolved] - private OsuGameBase game { get; set; } + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } private const string help_centre_url = "/help/wiki/Help_Centre#login"; @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.AccountCreation } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, OsuGame game, TextureStore textures) + private void load(OsuColour colours, TextureStore textures) { if (string.IsNullOrEmpty(api?.ProvidedUsername)) return; From e8bc2cac5bc058bdae888dcd0bc67e9d84b6fc47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 May 2021 13:36:39 +0900 Subject: [PATCH 190/304] Fix test not being marked as headless --- .../Editing/TestSceneHitObjectContainerEventBuffer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs index 5233cbc0be..592971dbaf 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -12,6 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Editing { + [HeadlessTest] public class TestSceneHitObjectContainerEventBuffer : OsuTestScene { private readonly TestHitObject testObj = new TestHitObject(); From 539e5179fe8ef9d9cd148882a063595295872815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 15:45:24 +0900 Subject: [PATCH 191/304] Tidy up content and bind event code --- .../Overlays/News/Sidebar/MonthSection.cs | 22 +++++++++---------- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 2fc88b3909..9fc4f49bd3 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -117,10 +117,10 @@ namespace osu.Game.Overlays.News.Sidebar } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, GameHost host) + private void load(OverlayColourProvider overlayColours, GameHost host) { - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Light1; + IdleColour = overlayColours.Light2; + HoverColour = overlayColours.Light1; TooltipText = "view in browser"; Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); @@ -131,9 +131,7 @@ namespace osu.Game.Overlays.News.Sidebar { public readonly BindableBool IsOpen = new BindableBool(); - protected override Container Content => content; - - private readonly FillFlowContainer content; + protected override Container Content { get; } public PostsContainer() { @@ -141,7 +139,7 @@ namespace osu.Game.Overlays.News.Sidebar AutoSizeAxes = Axes.Y; AutoSizeDuration = animation_duration; AutoSizeEasing = Easing.Out; - InternalChild = content = new FillFlowContainer + InternalChild = Content = new FillFlowContainer { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, @@ -155,24 +153,24 @@ namespace osu.Game.Overlays.News.Sidebar protected override void LoadComplete() { base.LoadComplete(); - IsOpen.BindValueChanged(_ => updateState(), true); + IsOpen.BindValueChanged(updateState, true); } - private void updateState() + private void updateState(ValueChangedEvent isOpen) { ClearTransforms(true); - if (IsOpen.Value) + if (isOpen.NewValue) { AutoSizeAxes = Axes.Y; - content.FadeIn(animation_duration, Easing.OutQuint); + Content.FadeIn(animation_duration, Easing.OutQuint); } else { AutoSizeAxes = Axes.None; this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); - content.FadeOut(animation_duration, Easing.OutQuint); + Content.FadeOut(animation_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 849cdbf659..b6bbdbb6d4 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.News.Sidebar private FillFlowContainer yearsFlow; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, Bindable metadata) + private void load(OverlayColourProvider overlayColours, Bindable metadata) { this.metadata.BindTo(metadata); @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.News.Sidebar new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3 + Colour = overlayColours.Background3 }, new Container { From 19a07b01073f821f4c02131f7834474396ea8d49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 15:48:31 +0900 Subject: [PATCH 192/304] IsOpen -> Expanded --- .../Overlays/News/Sidebar/MonthSection.cs | 21 ++++++++++--------- osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 9fc4f49bd3..b300a755f9 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.News.Sidebar { private const int animation_duration = 250; - public readonly BindableBool IsOpen = new BindableBool(); + public readonly BindableBool Expanded = new BindableBool(); public MonthSection(int month, int year, IEnumerable posts) { @@ -32,6 +32,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -41,11 +42,11 @@ namespace osu.Game.Overlays.News.Sidebar { new DropdownHeader(month, year) { - IsOpen = { BindTarget = IsOpen } + Expanded = { BindTarget = Expanded } }, new PostsContainer { - IsOpen = { BindTarget = IsOpen }, + Expanded = { BindTarget = Expanded }, Children = posts.Select(p => new PostButton(p)).ToArray() } } @@ -54,7 +55,7 @@ namespace osu.Game.Overlays.News.Sidebar private class DropdownHeader : OsuClickableContainer { - public readonly BindableBool IsOpen = new BindableBool(); + public readonly BindableBool Expanded = new BindableBool(); private readonly SpriteIcon icon; @@ -64,7 +65,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; Height = 15; - Action = IsOpen.Toggle; + Action = Expanded.Toggle; Children = new Drawable[] { new OsuSpriteText @@ -88,7 +89,7 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - IsOpen.BindValueChanged(open => + Expanded.BindValueChanged(open => { icon.Scale = new Vector2(1, open.NewValue ? -1 : 1); }, true); @@ -129,7 +130,7 @@ namespace osu.Game.Overlays.News.Sidebar private class PostsContainer : Container { - public readonly BindableBool IsOpen = new BindableBool(); + public readonly BindableBool Expanded = new BindableBool(); protected override Container Content { get; } @@ -153,14 +154,14 @@ namespace osu.Game.Overlays.News.Sidebar protected override void LoadComplete() { base.LoadComplete(); - IsOpen.BindValueChanged(updateState, true); + Expanded.BindValueChanged(updateState, true); } - private void updateState(ValueChangedEvent isOpen) + private void updateState(ValueChangedEvent expanded) { ClearTransforms(true); - if (isOpen.NewValue) + if (expanded.NewValue) { AutoSizeAxes = Axes.Y; Content.FadeIn(animation_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index b8d283b7e2..d14ad90ef4 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.News.Sidebar monthsFlow.Add(new MonthSection(month, year, posts) { - IsOpen = { Value = i == 0 } + Expanded = { Value = i == 0 } }); } } From cc5c702a92ddf6306e8b136699f299b1d1c7d484 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 17:31:47 +0900 Subject: [PATCH 193/304] Apply all properties after cast (looks cleaner) --- osu.Game.Rulesets.Mania/UI/Column.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 57d3f54716..9b5893b268 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -116,9 +116,9 @@ namespace osu.Game.Rulesets.Mania.UI { base.OnNewDrawableHitObject(drawableHitObject); - drawableHitObject.AccentColour.Value = AccentColour; - DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject; + + maniaObject.AccentColour.Value = AccentColour; maniaObject.CheckHittable = hitPolicy.IsHittable; } From a0f67ef3bc1bab4ae0d07115d6df97ef23bb88d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 18:57:52 +0900 Subject: [PATCH 194/304] Move scaling logic out of `OsuSelectionHandler` for reuse --- .../Edit/OsuSelectionHandler.cs | 18 +---------------- .../Compose/Components/SelectionHandler.cs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index aaf3517c9c..8cb86bc108 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -225,26 +225,10 @@ namespace osu.Game.Rulesets.Osu.Edit private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { scale = getClampedScale(hitObjects, reference, scale); - - // move the selection before scaling if dragging from top or left anchors. - float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; - float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; - Quad selectionQuad = getSurroundingQuad(hitObjects); foreach (var h in hitObjects) - { - var newPosition = h.Position; - - // guard against no-ops and NaN. - if (scale.X != 0 && selectionQuad.Width > 0) - newPosition.X = selectionQuad.TopLeft.X + xOffset + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - - if (scale.Y != 0 && selectionQuad.Height > 0) - newPosition.Y = selectionQuad.TopLeft.Y + yOffset + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); - - h.Position = newPosition; - } + h.Position = GetScaledPosition(reference, scale, selectionQuad, h.Position); } private (bool X, bool Y) isQuadInBounds(Quad quad) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index bfd5ab7afa..26328b4dc7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -375,6 +375,26 @@ namespace osu.Game.Screens.Edit.Compose.Components return position; } + /// + /// Given a scale vector, a surrounding quad for all selected objects, and a position, + /// will return the scaled position in screen space coordinates. + /// + protected static Vector2 GetScaledPosition(Anchor reference, Vector2 scale, Quad selectionQuad, Vector2 position) + { + // adjust the direction of scale depending on which side the user is dragging. + float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; + float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; + + // guard against no-ops and NaN. + if (scale.X != 0 && selectionQuad.Width > 0) + position.X = selectionQuad.TopLeft.X + xOffset + (position.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); + + if (scale.Y != 0 && selectionQuad.Height > 0) + position.Y = selectionQuad.TopLeft.Y + yOffset + (position.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); + + return position; + } + /// /// Returns a quad surrounding the provided points. /// From b2c736b42a52c91f8780ad00602a0dd18d1e6b83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 18:09:46 +0900 Subject: [PATCH 195/304] Combine and move `const` closer to usage --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 5 ----- osu.Game/Screens/Ranking/ResultsScreen.cs | 11 +++++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 4fca4759f2..c70b4dd35b 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -37,11 +37,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public const double ACCURACY_TRANSFORM_DURATION = 3000; - /// - /// Delay before the default applause sound is played to match the timing - /// - public const double APPLAUSE_DELAY = 1440; - /// /// Delay after for the rank text (A/B/C/D/S/SS) to appear. /// diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e25bbbe253..a0ea27b640 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -29,6 +29,11 @@ namespace osu.Game.Screens.Ranking { public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler { + /// + /// Delay before the default applause sound should be played, in order to match the grade display timing in . + /// + public const double APPLAUSE_DELAY = AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY + ScorePanel.RESIZE_DURATION + ScorePanel.TOP_LAYER_EXPAND_DELAY - 1440; + protected const float BACKGROUND_BLUR = 20; private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; @@ -196,10 +201,8 @@ namespace osu.Game.Screens.Ranking statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); - using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY, true)) - { - this.Delay(ScorePanel.RESIZE_DURATION + ScorePanel.TOP_LAYER_EXPAND_DELAY - AccuracyCircle.APPLAUSE_DELAY).Schedule(() => applauseSound?.Play()); - } + using (BeginDelayedSequence(APPLAUSE_DELAY)) + Schedule(() => applauseSound?.Play()); } protected override void Update() From 6c4709e7b4546faf39e7df98b4b143e461ebef9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 18:34:02 +0900 Subject: [PATCH 196/304] Fix `PlacementBlueprint` using the wrong beatmap when applying defaults Closes #12855. --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 4ad8c815fe..82e90399c9 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -36,7 +36,8 @@ namespace osu.Game.Rulesets.Edit [Resolved(canBeNull: true)] protected EditorClock EditorClock { get; private set; } - private readonly IBindable beatmap = new Bindable(); + [Resolved] + private EditorBeatmap beatmap { get; set; } private Bindable startTimeBindable; @@ -58,10 +59,8 @@ namespace osu.Game.Rulesets.Edit } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load() { - this.beatmap.BindTo(beatmap); - startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); } @@ -113,7 +112,7 @@ namespace osu.Game.Rulesets.Edit /// Invokes , /// refreshing and parameters for the . /// - protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); + protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; From 6a3c58b9adfc6184f23ae0eefa6c39c38bbfdc47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 19:58:55 +0900 Subject: [PATCH 197/304] Implement proper scaling algorithms --- .../Skinning/Editor/SkinSelectionHandler.cs | 98 +++++++++++++++++-- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2eb4ea107d..18c9341db9 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -34,9 +34,94 @@ namespace osu.Game.Skinning.Editor { adjustScaleFromAnchor(ref scale, anchor); - foreach (var c in SelectedBlueprints) - // TODO: this is temporary and will be fixed with a separate refactor of selection transform logic. - ((Drawable)c.Item).Scale += scale * 0.02f; + if (SelectedBlueprints.Count > 1) + { + var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => + b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + + // the selection quad is always upright, so use a rect to make mutating the values easier. + var adjustedRect = selectionQuad.AABBFloat; + + // for now aspect lock scale adjustments that occur at corners. + if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) + scale.Y = scale.X / selectionQuad.Width * selectionQuad.Height; + + if (anchor.HasFlagFast(Anchor.x0)) + { + adjustedRect.X -= scale.X; + adjustedRect.Width += scale.X; + } + else if (anchor.HasFlagFast(Anchor.x2)) + { + adjustedRect.Width += scale.X; + } + + if (anchor.HasFlagFast(Anchor.y0)) + { + adjustedRect.Y -= scale.Y; + adjustedRect.Height += scale.Y; + } + else if (anchor.HasFlagFast(Anchor.y2)) + { + adjustedRect.Height += scale.Y; + } + + // scale adjust should match that of the quad itself. + var scaledDelta = new Vector2( + adjustedRect.Width / selectionQuad.Width - 1, + adjustedRect.Height / selectionQuad.Height - 1 + ); + + foreach (var b in SelectedBlueprints) + { + var drawableItem = (Drawable)b.Item; + + if (SelectedBlueprints.Count > 1) + { + // each drawable's relative position should be maintained in the scaled quad. + var screenPosition = b.ScreenSpaceSelectionPoint; + + var relativePositionInOriginal = + new Vector2( + (screenPosition.X - selectionQuad.TopLeft.X) / selectionQuad.Width, + (screenPosition.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height + ); + + var newPositionInAdjusted = new Vector2( + adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, + adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y + ); + + drawableItem.Position = drawableItem.Parent.ToLocalSpace(newPositionInAdjusted) - drawableItem.AnchorPosition; + drawableItem.Scale += scaledDelta; + } + } + } + else + { + var blueprint = SelectedBlueprints.First(); + var drawableItem = (Drawable)blueprint.Item; + + // the number of local "pixels" the drag operation resulted in. + // our goal is to increase the drawable's draw size by this amount. + var scaledDelta = drawableItem.ScreenSpaceDeltaToParentSpace(scale); + + scaledDelta = new Vector2( + scaledDelta.X / drawableItem.DrawWidth, + scaledDelta.Y / drawableItem.DrawHeight + ); + + // handle the case where scaling with a centre origin needs double the adjustments to match + // user cursor movement. + if (drawableItem.Origin.HasFlagFast(Anchor.x1)) scaledDelta.X *= 2; + if (drawableItem.Origin.HasFlagFast(Anchor.y1)) scaledDelta.Y *= 2; + + // for now aspect lock scale adjustments that occur at corners. + if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) + scaledDelta.Y = scaledDelta.X; + + drawableItem.Scale += scaledDelta; + } return true; } @@ -158,13 +243,6 @@ namespace osu.Game.Skinning.Editor // reverse the scale direction if dragging from top or left. if ((reference & Anchor.x0) > 0) scale.X = -scale.X; if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - - // for now aspect lock scale adjustments that occur at corners. - if (!reference.HasFlagFast(Anchor.x1) && !reference.HasFlagFast(Anchor.y1)) - { - // TODO: temporary implementation - only dragging the corner handles across the X axis changes size. - scale.Y = scale.X; - } } public class AnchorMenuItem : TernaryStateMenuItem From 16ffedde8a8ac640e61f98c0949324c699a5b05f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:17:57 +0300 Subject: [PATCH 198/304] Add year parameter to GetNewsRequest --- osu.Game/Online/API/Requests/GetNewsRequest.cs | 8 +++++++- osu.Game/Overlays/News/Displays/FrontPageDisplay.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetNewsRequest.cs b/osu.Game/Online/API/Requests/GetNewsRequest.cs index 36d9dc0652..3eb29f1292 100644 --- a/osu.Game/Online/API/Requests/GetNewsRequest.cs +++ b/osu.Game/Online/API/Requests/GetNewsRequest.cs @@ -8,10 +8,12 @@ namespace osu.Game.Online.API.Requests { public class GetNewsRequest : APIRequest { + private readonly int year; private readonly Cursor cursor; - public GetNewsRequest(Cursor cursor = null) + public GetNewsRequest(int year = 0, Cursor cursor = null) { + this.year = year; this.cursor = cursor; } @@ -19,6 +21,10 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); req.AddCursor(cursor); + + if (year != 0) + req.AddParameter("year", year.ToString()); + return req; } diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index a1bc6c650b..45e11a6f15 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.News.Displays { request?.Cancel(); - request = new GetNewsRequest(lastCursor); + request = new GetNewsRequest(cursor: lastCursor); request.Success += response => Schedule(() => onSuccess(response)); api.PerformAsync(request); } From 150ed01c627e49e00a75f8c7b3a8a68e0bfea424 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:22:55 +0300 Subject: [PATCH 199/304] Make NewsSidebar scrollable --- osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index d14ad90ef4..9e397e78c8 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Framework.Graphics.Shapes; using osuTK; using System.Linq; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.News.Sidebar { @@ -31,30 +32,55 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4 }, + new Box + { + RelativeSizeAxes = Axes.Y, + Width = OsuScrollContainer.SCROLL_BAR_HEIGHT, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colourProvider.Background3, + Alpha = 0.5f + }, new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin + Child = new OsuScrollContainer { - Vertical = 20, - Left = 50, - Right = 30 - }, - Child = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 20), - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Child = new Container { - new YearsPanel(), - monthsFlow = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 3 }, // Addeded 3px back + Child = new Container { - AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Vertical = 20, + Left = 50, + Right = 30 + }, + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new YearsPanel(), + monthsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + } + } + } } } } From 6cc4ffadabda1eb13d8b8d96a7a7d311f9314721 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:28:12 +0300 Subject: [PATCH 200/304] Implement sticky container for sidebar in NewsOverlay --- osu.Game/Overlays/NewsOverlay.cs | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 5beb285216..ddd328c860 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -1,11 +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 System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Overlays.News; using osu.Game.Overlays.News.Displays; +using osu.Game.Overlays.News.Sidebar; namespace osu.Game.Overlays { @@ -13,9 +16,44 @@ namespace osu.Game.Overlays { private readonly Bindable article = new Bindable(null); + protected override Container Content => content; + + private readonly Container content; + private readonly Container sidebarContainer; + public NewsOverlay() : base(OverlayColourScheme.Purple, false) { + base.Content.Add(new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + sidebarContainer = new Container + { + AutoSizeAxes = Axes.X, + Child = new NewsSidebar() + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } + }); } protected override void LoadComplete() @@ -90,6 +128,14 @@ namespace osu.Game.Overlays }, (cancellationToken = new CancellationTokenSource()).Token); } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + sidebarContainer.Height = DrawHeight; + sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); + } + protected override void Dispose(bool isDisposing) { cancellationToken?.Cancel(); From e3ed9b8135b93c3b14685de298f1a6e25ec44f6b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:36:05 +0300 Subject: [PATCH 201/304] Implement sidebar metadata handling in NewsOverlay --- .../News/Displays/FrontPageDisplay.cs | 20 ++++++++- osu.Game/Overlays/NewsOverlay.cs | 44 ++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index 45e11a6f15..7691e4a901 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using System.Threading; using osu.Framework.Allocation; @@ -15,6 +16,8 @@ namespace osu.Game.Overlays.News.Displays { public class FrontPageDisplay : CompositeDrawable { + public Action ResponseReceived; + [Resolved] private IAPIProvider api { get; set; } @@ -24,6 +27,13 @@ namespace osu.Game.Overlays.News.Displays private GetNewsRequest request; private Cursor lastCursor; + private readonly int year; + + public FrontPageDisplay(int year = 0) + { + this.year = year; + } + [BackgroundDependencyLoader] private void load() { @@ -74,13 +84,15 @@ namespace osu.Game.Overlays.News.Displays { request?.Cancel(); - request = new GetNewsRequest(cursor: lastCursor); + request = new GetNewsRequest(year, lastCursor); request.Success += response => Schedule(() => onSuccess(response)); api.PerformAsync(request); } private CancellationTokenSource cancellationToken; + private bool initialLoad = true; + private void onSuccess(GetNewsResponse response) { cancellationToken?.Cancel(); @@ -101,6 +113,12 @@ namespace osu.Game.Overlays.News.Displays content.Add(loaded); showMore.IsLoading = false; showMore.Alpha = lastCursor == null ? 0 : 1; + + if (initialLoad) + { + ResponseReceived?.Invoke(response); + initialLoad = false; + } }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index ddd328c860..d3f407fc0f 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -20,6 +20,7 @@ namespace osu.Game.Overlays private readonly Container content; private readonly Container sidebarContainer; + private readonly NewsSidebar sidebar; public NewsOverlay() : base(OverlayColourScheme.Purple, false) @@ -44,7 +45,7 @@ namespace osu.Game.Overlays sidebarContainer = new Container { AutoSizeAxes = Axes.X, - Child = new NewsSidebar() + Child = sidebar = new NewsSidebar() }, content = new Container { @@ -94,6 +95,12 @@ namespace osu.Game.Overlays Show(); } + public void ShowYear(int year) + { + showYear(year); + Show(); + } + public void ShowArticle(string slug) { article.Value = slug; @@ -102,6 +109,14 @@ namespace osu.Game.Overlays private CancellationTokenSource cancellationToken; + private void showYear(int year) + { + cancellationToken?.Cancel(); + Loading.Show(); + + loadFrontPage(year); + } + private void onArticleChanged(ValueChangedEvent e) { cancellationToken?.Cancel(); @@ -109,13 +124,33 @@ namespace osu.Game.Overlays if (e.NewValue == null) { - Header.SetFrontPage(); - LoadDisplay(new FrontPageDisplay()); + loadFrontPage(); return; } - Header.SetArticle(e.NewValue); + loadArticle(e.NewValue); + } + + private void loadFrontPage(int year = 0) + { + Header.SetFrontPage(); + + var page = new FrontPageDisplay(year); + page.ResponseReceived += r => + { + sidebar.Metadata.Value = r.SidebarMetadata; + Loading.Hide(); + }; + LoadDisplay(page); + } + + private void loadArticle(string article) + { + Header.SetArticle(article); + + // Temporary, should be handled by ArticleDisplay later LoadDisplay(Empty()); + Loading.Hide(); } protected void LoadDisplay(Drawable display) @@ -124,7 +159,6 @@ namespace osu.Game.Overlays LoadComponentAsync(display, loaded => { Child = loaded; - Loading.Hide(); }, (cancellationToken = new CancellationTokenSource()).Token); } From d60478851f8bf9a63aa158b29b9a3a3d784451c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:38:53 +0300 Subject: [PATCH 202/304] Add proper action to YearButton --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index b6bbdbb6d4..232b995cd6 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -81,6 +81,9 @@ namespace osu.Game.Overlays.News.Sidebar { public int Year { get; } + [Resolved(canBeNull: true)] + private NewsOverlay overlay { get; set; } + private readonly bool isCurrent; public YearButton(int year, bool isCurrent) @@ -106,7 +109,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = isCurrent ? Color4.White : colourProvider.Light2; HoverColour = isCurrent ? Color4.White : colourProvider.Light1; - Action = () => { }; // Avoid button being disabled since there's no proper action assigned. + Action = () => overlay?.ShowYear(Year); } } } From 14af86d6c55ee8ccae7ae0b531b537eb0d9cbbb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 21:46:41 +0900 Subject: [PATCH 203/304] Use the same code path for all scaling --- .../Skinning/Editor/SkinSelectionHandler.cs | 127 +++++++----------- 1 file changed, 48 insertions(+), 79 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 18c9341db9..af013bfd52 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -34,92 +34,61 @@ namespace osu.Game.Skinning.Editor { adjustScaleFromAnchor(ref scale, anchor); - if (SelectedBlueprints.Count > 1) + var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => + b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + + // the selection quad is always upright, so use a rect to make mutating the values easier. + var adjustedRect = selectionQuad.AABBFloat; + + // for now aspect lock scale adjustments that occur at corners. + if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) + scale.Y = scale.X / selectionQuad.Width * selectionQuad.Height; + + if (anchor.HasFlagFast(Anchor.x0)) { - var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => - b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); - - // the selection quad is always upright, so use a rect to make mutating the values easier. - var adjustedRect = selectionQuad.AABBFloat; - - // for now aspect lock scale adjustments that occur at corners. - if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) - scale.Y = scale.X / selectionQuad.Width * selectionQuad.Height; - - if (anchor.HasFlagFast(Anchor.x0)) - { - adjustedRect.X -= scale.X; - adjustedRect.Width += scale.X; - } - else if (anchor.HasFlagFast(Anchor.x2)) - { - adjustedRect.Width += scale.X; - } - - if (anchor.HasFlagFast(Anchor.y0)) - { - adjustedRect.Y -= scale.Y; - adjustedRect.Height += scale.Y; - } - else if (anchor.HasFlagFast(Anchor.y2)) - { - adjustedRect.Height += scale.Y; - } - - // scale adjust should match that of the quad itself. - var scaledDelta = new Vector2( - adjustedRect.Width / selectionQuad.Width - 1, - adjustedRect.Height / selectionQuad.Height - 1 - ); - - foreach (var b in SelectedBlueprints) - { - var drawableItem = (Drawable)b.Item; - - if (SelectedBlueprints.Count > 1) - { - // each drawable's relative position should be maintained in the scaled quad. - var screenPosition = b.ScreenSpaceSelectionPoint; - - var relativePositionInOriginal = - new Vector2( - (screenPosition.X - selectionQuad.TopLeft.X) / selectionQuad.Width, - (screenPosition.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height - ); - - var newPositionInAdjusted = new Vector2( - adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, - adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y - ); - - drawableItem.Position = drawableItem.Parent.ToLocalSpace(newPositionInAdjusted) - drawableItem.AnchorPosition; - drawableItem.Scale += scaledDelta; - } - } + adjustedRect.X -= scale.X; + adjustedRect.Width += scale.X; } - else + else if (anchor.HasFlagFast(Anchor.x2)) { - var blueprint = SelectedBlueprints.First(); - var drawableItem = (Drawable)blueprint.Item; + adjustedRect.Width += scale.X; + } - // the number of local "pixels" the drag operation resulted in. - // our goal is to increase the drawable's draw size by this amount. - var scaledDelta = drawableItem.ScreenSpaceDeltaToParentSpace(scale); + if (anchor.HasFlagFast(Anchor.y0)) + { + adjustedRect.Y -= scale.Y; + adjustedRect.Height += scale.Y; + } + else if (anchor.HasFlagFast(Anchor.y2)) + { + adjustedRect.Height += scale.Y; + } - scaledDelta = new Vector2( - scaledDelta.X / drawableItem.DrawWidth, - scaledDelta.Y / drawableItem.DrawHeight + // scale adjust should match that of the quad itself. + var scaledDelta = new Vector2( + adjustedRect.Width / selectionQuad.Width - 1, + adjustedRect.Height / selectionQuad.Height - 1 + ); + + foreach (var b in SelectedBlueprints) + { + var drawableItem = (Drawable)b.Item; + + // each drawable's relative position should be maintained in the scaled quad. + var screenPosition = b.ScreenSpaceSelectionPoint; + + var relativePositionInOriginal = + new Vector2( + (screenPosition.X - selectionQuad.TopLeft.X) / selectionQuad.Width, + (screenPosition.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height + ); + + var newPositionInAdjusted = new Vector2( + adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, + adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y ); - // handle the case where scaling with a centre origin needs double the adjustments to match - // user cursor movement. - if (drawableItem.Origin.HasFlagFast(Anchor.x1)) scaledDelta.X *= 2; - if (drawableItem.Origin.HasFlagFast(Anchor.y1)) scaledDelta.Y *= 2; - - // for now aspect lock scale adjustments that occur at corners. - if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) - scaledDelta.Y = scaledDelta.X; - + drawableItem.Position = drawableItem.Parent.ToLocalSpace(newPositionInAdjusted) - drawableItem.AnchorPosition; drawableItem.Scale += scaledDelta; } From a55879e5115895c940411bc9d7000532e32d5219 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 01:47:31 +0900 Subject: [PATCH 204/304] Fix oversights in scale algorithm --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index af013bfd52..2f9611ba65 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -32,6 +32,9 @@ namespace osu.Game.Skinning.Editor public override bool HandleScale(Vector2 scale, Anchor anchor) { + // convert scale to screen space + scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); + adjustScaleFromAnchor(ref scale, anchor); var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => @@ -66,8 +69,8 @@ namespace osu.Game.Skinning.Editor // scale adjust should match that of the quad itself. var scaledDelta = new Vector2( - adjustedRect.Width / selectionQuad.Width - 1, - adjustedRect.Height / selectionQuad.Height - 1 + adjustedRect.Width / selectionQuad.Width, + adjustedRect.Height / selectionQuad.Height ); foreach (var b in SelectedBlueprints) @@ -89,7 +92,7 @@ namespace osu.Game.Skinning.Editor ); drawableItem.Position = drawableItem.Parent.ToLocalSpace(newPositionInAdjusted) - drawableItem.AnchorPosition; - drawableItem.Scale += scaledDelta; + drawableItem.Scale *= scaledDelta; } return true; From 00ed6993400e35f6ba5c7416219932e90e63bd08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 01:53:24 +0900 Subject: [PATCH 205/304] Fix origin specifications using incorrect flags --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 32d7f3525f..5d0263772d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters colourBarsEarly = new Container { Anchor = Anchor.CentreLeft, - Origin = Anchor.x2, + Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Both, Height = 0.5f, Scale = new Vector2(1, -1), @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters colourBarsLate = new Container { Anchor = Anchor.CentreLeft, - Origin = Anchor.x2, + Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Both, Height = 0.5f, }, From 22337e0fc79c6e45783eb5af137db8df93378244 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 01:59:30 +0900 Subject: [PATCH 206/304] Add comment explaining why origin is flipped --- osu.Game/Skinning/DefaultSkin.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 84f40df0cf..ba31816a07 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -93,8 +93,9 @@ namespace osu.Game.Skinning if (hitError2 != null) { hitError2.Anchor = Anchor.CentreRight; - hitError2.Origin = Anchor.CentreLeft; hitError2.Scale = new Vector2(-1, 1); + // origin flipped to match scale above. + hitError2.Origin = Anchor.CentreLeft; } } }) From eb5db8ff0357a0814491437a59d8b427d84bf2fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 02:01:25 +0900 Subject: [PATCH 207/304] Disable border display on skin editor to avoid crashes This wasn't being displayed correctly anyway, so rather than fixing let's just remove it for now. Closes #12868. --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 2d7cae71ff..88020896bb 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -65,8 +65,6 @@ namespace osu.Game.Skinning.Editor if (visibility.NewValue == Visibility.Visible) { target.Masking = true; - target.BorderThickness = 5; - target.BorderColour = colours.Yellow; target.AllowScaling = false; target.RelativePositionAxes = Axes.Both; @@ -75,7 +73,6 @@ namespace osu.Game.Skinning.Editor } else { - target.BorderThickness = 0; target.AllowScaling = true; target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => target.Masking = false); From e9cab29134eec87685d6a343babcfb361b530b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 May 2021 20:48:06 +0200 Subject: [PATCH 208/304] Cache editor beatmap in placement blueprint test scene --- osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 78a6bcc3db..2dc77fa72a 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -33,8 +33,12 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new EditorClock()); + var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + dependencies.CacheAs(new EditorBeatmap(playable)); + return dependencies; } From 85a3027f1ba4107bee90ee3d2c7492d93b1546f4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 19 May 2021 13:58:41 -0700 Subject: [PATCH 209/304] Add failing test --- .../Visual/Online/TestSceneAccountCreationOverlay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index 3d65e7e4ba..b120a9b4db 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Overlays; +using osu.Game.Overlays.AccountCreation; +using osu.Game.Overlays.Settings; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -51,6 +55,9 @@ namespace osu.Game.Tests.Visual.Online AddStep("show manually", () => accountCreation.Show()); AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible); + AddStep("click button", () => accountCreation.ChildrenOfType().Single().Click()); + AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType().Single().IsPresent); + AddStep("log back in", () => API.Login("dummy", "password")); AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden); } From 3da2cdfd05314d975cf3a979709646729b4bca13 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 19 May 2021 14:06:21 -0700 Subject: [PATCH 210/304] Fix nullref in test --- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index 2605a0a6d6..6efbf8896f 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.AccountCreation public override void OnEntering(IScreen last) { - if (string.IsNullOrEmpty(api?.ProvidedUsername) || game.UseDevelopmentServer) + if (string.IsNullOrEmpty(api?.ProvidedUsername) || game?.UseDevelopmentServer == true) { this.FadeOut(); this.Push(new ScreenEntry()); From f1fd40dcca30d348cbdad21ded8f6cef557a4fb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 13:19:00 +0900 Subject: [PATCH 211/304] Fix test not working for various reasons --- .../Visual/Online/TestSceneAccountCreationOverlay.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index b120a9b4db..437c5b07c9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -40,8 +40,6 @@ namespace osu.Game.Tests.Visual.Online [BackgroundDependencyLoader] private void load() { - API.Logout(); - localUser = API.LocalUser.GetBoundCopy(); localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true); } @@ -50,13 +48,13 @@ namespace osu.Game.Tests.Visual.Online public void TestOverlayVisibility() { AddStep("start hidden", () => accountCreation.Hide()); - AddStep("log out", API.Logout); + AddStep("log out", () => API.Logout()); AddStep("show manually", () => accountCreation.Show()); AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible); AddStep("click button", () => accountCreation.ChildrenOfType().Single().Click()); - AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType().Single().IsPresent); + AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType().SingleOrDefault()?.IsPresent == true); AddStep("log back in", () => API.Login("dummy", "password")); AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden); From 3c201fb8e78a2c04e541b03cd9b7a1435c98468e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 13:20:35 +0900 Subject: [PATCH 212/304] Standardise `canBeNull` specification --- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index 6efbf8896f..3d46e9ed94 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.AccountCreation private OsuTextFlowContainer multiAccountExplanationText; private LinkFlowContainer furtherAssistance; - [Resolved(CanBeNull = true)] + [Resolved(canBeNull: true)] private IAPIProvider api { get; set; } [Resolved(canBeNull: true)] From 8f5b28d26452211a913f7e2a2b67aece638367a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 13:51:08 +0900 Subject: [PATCH 213/304] Fix "folder missing" message showing incorrectly for beatmaps folder --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5e975de77c..dcbfbf1332 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -65,7 +65,7 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - protected override string ImportFromStablePath => "."; + protected override string ImportFromStablePath => string.Empty; protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage(); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 550daf36b5..8efd451857 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -691,10 +691,12 @@ namespace osu.Game.Database { var storage = PrepareStableStorage(stableStorage); + // Handle situations like when the user does not have a Skins folder. if (!storage.ExistsDirectory(ImportFromStablePath)) { - // This handles situations like when the user does not have a Skins folder - Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + string fullPath = storage.GetFullPath(ImportFromStablePath); + + Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } From 713f69ea5505d21359a34c3d9bb3c8980580709b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 14:11:42 +0900 Subject: [PATCH 214/304] Tidy up load process --- osu.Game/Overlays/NewsOverlay.cs | 34 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index d3f407fc0f..f7294dd880 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -22,6 +22,8 @@ namespace osu.Game.Overlays private readonly Container sidebarContainer; private readonly NewsSidebar sidebar; + private CancellationTokenSource cancellationToken; + public NewsOverlay() : base(OverlayColourScheme.Purple, false) { @@ -97,7 +99,7 @@ namespace osu.Game.Overlays public void ShowYear(int year) { - showYear(year); + loadFrontPage(year); Show(); } @@ -107,32 +109,18 @@ namespace osu.Game.Overlays Show(); } - private CancellationTokenSource cancellationToken; - - private void showYear(int year) - { - cancellationToken?.Cancel(); - Loading.Show(); - - loadFrontPage(year); - } - private void onArticleChanged(ValueChangedEvent e) { - cancellationToken?.Cancel(); - Loading.Show(); - if (e.NewValue == null) - { loadFrontPage(); - return; - } - - loadArticle(e.NewValue); + else + loadArticle(e.NewValue); } private void loadFrontPage(int year = 0) { + beginLoading(); + Header.SetFrontPage(); var page = new FrontPageDisplay(year); @@ -146,6 +134,8 @@ namespace osu.Game.Overlays private void loadArticle(string article) { + beginLoading(); + Header.SetArticle(article); // Temporary, should be handled by ArticleDisplay later @@ -153,6 +143,12 @@ namespace osu.Game.Overlays Loading.Hide(); } + private void beginLoading() + { + cancellationToken?.Cancel(); + Loading.Show(); + } + protected void LoadDisplay(Drawable display) { ScrollFlow.ScrollToStart(); From ac8efdeabdceac44fd261febeae971bb2deaa087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 14:12:34 +0900 Subject: [PATCH 215/304] Move private methods down --- osu.Game/Overlays/NewsOverlay.cs | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index f7294dd880..280e224255 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays private CancellationTokenSource cancellationToken; + private bool displayUpdateRequired = true; + public NewsOverlay() : base(OverlayColourScheme.Purple, false) { @@ -72,8 +74,6 @@ namespace osu.Game.Overlays ShowFrontPage = ShowFrontPage }; - private bool displayUpdateRequired = true; - protected override void PopIn() { base.PopIn(); @@ -109,6 +109,23 @@ namespace osu.Game.Overlays Show(); } + protected void LoadDisplay(Drawable display) + { + ScrollFlow.ScrollToStart(); + LoadComponentAsync(display, loaded => + { + Child = loaded; + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + sidebarContainer.Height = DrawHeight; + sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); + } + private void onArticleChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -149,23 +166,6 @@ namespace osu.Game.Overlays Loading.Show(); } - protected void LoadDisplay(Drawable display) - { - ScrollFlow.ScrollToStart(); - LoadComponentAsync(display, loaded => - { - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - sidebarContainer.Height = DrawHeight; - sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); - } - protected override void Dispose(bool isDisposing) { cancellationToken?.Cancel(); From 673ca4c2a11252b24219222dd9f5f971fc523102 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 14:30:40 +0900 Subject: [PATCH 216/304] Tidy up content container specification --- osu.Game/Overlays/NewsOverlay.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 280e224255..b082614a6e 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -16,12 +16,11 @@ namespace osu.Game.Overlays { private readonly Bindable article = new Bindable(null); - protected override Container Content => content; - - private readonly Container content; private readonly Container sidebarContainer; private readonly NewsSidebar sidebar; + private readonly Container content; + private CancellationTokenSource cancellationToken; private bool displayUpdateRequired = true; @@ -29,7 +28,7 @@ namespace osu.Game.Overlays public NewsOverlay() : base(OverlayColourScheme.Purple, false) { - base.Content.Add(new GridContainer + Child = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -58,7 +57,7 @@ namespace osu.Game.Overlays } } } - }); + }; } protected override void LoadComplete() @@ -112,16 +111,12 @@ namespace osu.Game.Overlays protected void LoadDisplay(Drawable display) { ScrollFlow.ScrollToStart(); - LoadComponentAsync(display, loaded => - { - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); + LoadComponentAsync(display, loaded => content.Child = loaded, (cancellationToken = new CancellationTokenSource()).Token); } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - sidebarContainer.Height = DrawHeight; sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); } From 0489ae719de8109fdf1dc3cc26ffe65ccf609b7e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 20 May 2021 14:53:33 +0900 Subject: [PATCH 217/304] Don't couple `PoolableDrawableWithLifetime` lifetime with its entry It turns out the incompatibility with `LifetimeManagementContainer` causes more issues than anticipated. --- .../Pooling/PoolableDrawableWithLifetime.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index ed0430012a..64e1ac16bd 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -3,7 +3,6 @@ #nullable enable -using System; using System.Diagnostics; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; @@ -27,13 +26,14 @@ namespace osu.Game.Rulesets.Objects.Pooling /// protected bool HasEntryApplied { get; private set; } + // Drawable's lifetime gets out of sync with entry's lifetime if entry's lifetime is modified. + // We cannot delegate getter to `Entry.LifetimeStart` because it is incompatible with `LifetimeManagementContainer` due to how lifetime change is detected. public override double LifetimeStart { - get => Entry?.LifetimeStart ?? double.MinValue; + get => base.LifetimeStart; set { - if (Entry == null && LifetimeStart != value) - throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); + base.LifetimeStart = value; if (Entry != null) Entry.LifetimeStart = value; @@ -42,11 +42,10 @@ namespace osu.Game.Rulesets.Objects.Pooling public override double LifetimeEnd { - get => Entry?.LifetimeEnd ?? double.MaxValue; + get => base.LifetimeEnd; set { - if (Entry == null && LifetimeEnd != value) - throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); + base.LifetimeEnd = value; if (Entry != null) Entry.LifetimeEnd = value; @@ -80,7 +79,12 @@ namespace osu.Game.Rulesets.Objects.Pooling free(); Entry = entry; + + base.LifetimeStart = entry.LifetimeStart; + base.LifetimeEnd = entry.LifetimeEnd; + OnApply(entry); + HasEntryApplied = true; } @@ -112,7 +116,11 @@ namespace osu.Game.Rulesets.Objects.Pooling Debug.Assert(Entry != null && HasEntryApplied); OnFree(Entry); + Entry = null; + base.LifetimeStart = double.MinValue; + base.LifetimeEnd = double.MaxValue; + HasEntryApplied = false; } } From 489caebf5996f1ca676e7d1700e44da8fd21110e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:15:19 +0900 Subject: [PATCH 218/304] Move bind `LoadComplete` code out of constructor --- osu.Game/Overlays/News/NewsHeader.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 63174128e7..94bfd62c32 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -19,13 +19,18 @@ namespace osu.Game.Overlays.News { TabControl.AddItem(front_page_string); + article.BindValueChanged(onArticleChanged, true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(e => { if (e.NewValue == front_page_string) ShowFrontPage?.Invoke(); }); - - article.BindValueChanged(onArticleChanged, true); } public void SetFrontPage() => article.Value = null; From d4530313aa8605f9071d9a40f8e1d0bf192d7014 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:15:46 +0900 Subject: [PATCH 219/304] Tidy event parameter naming --- osu.Game/Overlays/NewsOverlay.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index b082614a6e..df564704fa 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -68,10 +68,7 @@ namespace osu.Game.Overlays article.BindValueChanged(onArticleChanged); } - protected override NewsHeader CreateHeader() => new NewsHeader - { - ShowFrontPage = ShowFrontPage - }; + protected override NewsHeader CreateHeader() => new NewsHeader { ShowFrontPage = ShowFrontPage }; protected override void PopIn() { @@ -121,12 +118,12 @@ namespace osu.Game.Overlays sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); } - private void onArticleChanged(ValueChangedEvent e) + private void onArticleChanged(ValueChangedEvent article) { - if (e.NewValue == null) + if (article.NewValue == null) loadFrontPage(); else - loadArticle(e.NewValue); + loadArticle(article.NewValue); } private void loadFrontPage(int year = 0) From 9267d23dc282976abbacebd82281da2ba71cfd3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:23:49 +0900 Subject: [PATCH 220/304] Make year nullable rather than defaulting to zero --- osu.Game/Online/API/Requests/GetNewsRequest.cs | 8 ++++---- .../Displays/{FrontPageDisplay.cs => ArticleListing.cs} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/News/Displays/{FrontPageDisplay.cs => ArticleListing.cs} (100%) diff --git a/osu.Game/Online/API/Requests/GetNewsRequest.cs b/osu.Game/Online/API/Requests/GetNewsRequest.cs index 3eb29f1292..992ccc6d59 100644 --- a/osu.Game/Online/API/Requests/GetNewsRequest.cs +++ b/osu.Game/Online/API/Requests/GetNewsRequest.cs @@ -8,10 +8,10 @@ namespace osu.Game.Online.API.Requests { public class GetNewsRequest : APIRequest { - private readonly int year; + private readonly int? year; private readonly Cursor cursor; - public GetNewsRequest(int year = 0, Cursor cursor = null) + public GetNewsRequest(int? year = null, Cursor cursor = null) { this.year = year; this.cursor = cursor; @@ -22,8 +22,8 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddCursor(cursor); - if (year != 0) - req.AddParameter("year", year.ToString()); + if (year.HasValue) + req.AddParameter("year", year.Value.ToString()); return req; } diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs similarity index 100% rename from osu.Game/Overlays/News/Displays/FrontPageDisplay.cs rename to osu.Game/Overlays/News/Displays/ArticleListing.cs From 958d51141da905b123f6167a0a60d506862b5f86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:24:01 +0900 Subject: [PATCH 221/304] Rename `FrontPageDisplay` to `ArticleListing` --- osu.Game/Overlays/News/Displays/ArticleListing.cs | 13 ++++++++++--- osu.Game/Overlays/NewsOverlay.cs | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index 7691e4a901..e713b3de84 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -14,7 +14,10 @@ using osuTK; namespace osu.Game.Overlays.News.Displays { - public class FrontPageDisplay : CompositeDrawable + /// + /// Lists articles in a vertical flow for a specified year. + /// + public class ArticleListing : CompositeDrawable { public Action ResponseReceived; @@ -27,9 +30,13 @@ namespace osu.Game.Overlays.News.Displays private GetNewsRequest request; private Cursor lastCursor; - private readonly int year; + private readonly int? year; - public FrontPageDisplay(int year = 0) + /// + /// Instantiate a listing for the specified year. + /// + /// The year to load articles from. If null, will show the most recent articles. + public ArticleListing(int? year = null) { this.year = year; } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index df564704fa..510cdba020 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays Header.SetFrontPage(); - var page = new FrontPageDisplay(year); + var page = new ArticleListing(year); page.ResponseReceived += r => { sidebar.Metadata.Value = r.SidebarMetadata; From abf96db54535e8e24a818db00ee6d19e921217ce Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 20 May 2021 15:27:08 +0900 Subject: [PATCH 222/304] Add regression test for the pattern of using DHO proxy in `LifetimeManagementContainer` --- .../Gameplay/TestSceneProxyContainer.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs b/osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs new file mode 100644 index 0000000000..9975c65085 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs @@ -0,0 +1,85 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneProxyContainer : OsuTestScene + { + private HitObjectContainer hitObjectContainer; + private ProxyContainer proxyContainer; + private readonly ManualClock clock = new ManualClock(); + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = new Container + { + Children = new Drawable[] + { + hitObjectContainer = new HitObjectContainer(), + proxyContainer = new ProxyContainer() + }, + Clock = new FramedClock(clock) + }; + clock.CurrentTime = 0; + }); + + [Test] + public void TestProxyLifetimeManagement() + { + AddStep("Add proxy drawables", () => + { + addProxy(new TestDrawableHitObject(1000)); + addProxy(new TestDrawableHitObject(3000)); + addProxy(new TestDrawableHitObject(5000)); + }); + + AddStep($"time = 1000", () => clock.CurrentTime = 1000); + AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1); + AddStep($"time = 5000", () => clock.CurrentTime = 5000); + AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1); + AddStep($"time = 6000", () => clock.CurrentTime = 6000); + AddAssert("No proxy is alive", () => proxyContainer.AliveChildren.Count == 0); + } + + private void addProxy(DrawableHitObject drawableHitObject) + { + hitObjectContainer.Add(drawableHitObject); + proxyContainer.AddProxy(drawableHitObject); + } + + private class ProxyContainer : LifetimeManagementContainer + { + public IReadOnlyList AliveChildren => AliveInternalChildren; + + public void AddProxy(Drawable d) => AddInternal(d.CreateProxy()); + } + + private class TestDrawableHitObject : DrawableHitObject + { + protected override double InitialLifetimeOffset => 100; + + public TestDrawableHitObject(double startTime) + : base(new HitObject { StartTime = startTime }) + { + } + + protected override void UpdateInitialTransforms() + { + LifetimeEnd = LifetimeStart + 500; + } + } + } +} From d197a7f6f5f539ec2e75f1bf2e7935bc8642d9a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 15:39:45 +0900 Subject: [PATCH 223/304] Rename multiplayer client classes --- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../Online/Multiplayer/MultiplayerClient.cs | 645 +++++++++++++++--- .../Multiplayer/OnlineMultiplayerClient.cs | 158 +++++ .../Multiplayer/StatefulMultiplayerClient.cs | 642 ----------------- osu.Game/OsuGameBase.cs | 4 +- .../CreateMultiplayerMatchButton.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../OnlinePlay/Multiplayer/Multiplayer.cs | 2 +- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 2 +- .../Multiplayer/MultiplayerRoomComposite.cs | 2 +- .../Multiplayer/MultiplayerRoomManager.cs | 2 +- .../Participants/ParticipantsListHeader.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../HUD/MultiplayerGameplayLeaderboard.cs | 2 +- .../Multiplayer/MultiplayerTestScene.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../TestMultiplayerRoomContainer.cs | 2 +- 20 files changed, 742 insertions(+), 739 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs delete mode 100644 osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index bba7e2b391..424efb255b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer { - [Cached(typeof(StatefulMultiplayerClient))] + [Cached(typeof(MultiplayerClient))] public readonly TestMultiplayerClient Client; public TestMultiplayer() diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 4529dfd0a7..2e65f7cf1c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -3,132 +3,621 @@ #nullable enable +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Users; +using osu.Game.Utils; namespace osu.Game.Online.Multiplayer { - public class MultiplayerClient : StatefulMultiplayerClient + public abstract class MultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer { - private readonly string endpoint; + /// + /// Invoked when any change occurs to the multiplayer room. + /// + public event Action? RoomUpdated; - private IHubClientConnector? connector; + /// + /// Invoked when the multiplayer server requests the current beatmap to be loaded into play. + /// + public event Action? LoadRequested; - public override IBindable IsConnected { get; } = new BindableBool(); + /// + /// Invoked when the multiplayer server requests gameplay to be started. + /// + public event Action? MatchStarted; - private HubConnection? connection => connector?.CurrentConnection; + /// + /// Invoked when the multiplayer server has finished collating results. + /// + public event Action? ResultsReady; - public MultiplayerClient(EndpointConfiguration endpoints) + /// + /// Whether the is currently connected. + /// This is NOT thread safe and usage should be scheduled. + /// + public abstract IBindable IsConnected { get; } + + /// + /// The joined . + /// + public MultiplayerRoom? Room { get; private set; } + + /// + /// The users in the joined which are participating in the current gameplay loop. + /// + public readonly BindableList CurrentMatchPlayingUserIds = new BindableList(); + + public readonly Bindable CurrentMatchPlayingItem = new Bindable(); + + /// + /// The corresponding to the local player, if available. + /// + public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id); + + /// + /// Whether the is the host in . + /// + public bool IsHost { - endpoint = endpoints.MultiplayerEndpointUrl; - } - - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - connector = api.GetHubConnector(nameof(MultiplayerClient), endpoint); - - if (connector != null) + get { - connector.ConfigureConnection = connection => - { - // this is kind of SILLY - // https://github.com/dotnet/aspnetcore/issues/15198 - connection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); - connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); - connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); - connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); - connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); - connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); - connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); - connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); - connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); - connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); - connection.On(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged); - }; - - IsConnected.BindTo(connector.IsConnected); + var localUser = LocalUser; + return localUser != null && Room?.Host != null && localUser.Equals(Room.Host); } } - protected override Task JoinRoom(long roomId) - { - if (!IsConnected.Value) - return Task.FromCanceled(new CancellationToken(true)); + [Resolved] + protected IAPIProvider API { get; private set; } = null!; - return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoom), roomId); + [Resolved] + protected RulesetStore Rulesets { get; private set; } = null!; + + [Resolved] + private UserLookupCache userLookupCache { get; set; } = null!; + + private Room? apiRoom; + + [BackgroundDependencyLoader] + private void load() + { + IsConnected.BindValueChanged(connected => + { + // clean up local room state on server disconnect. + if (!connected.NewValue && Room != null) + { + Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); + LeaveRoom(); + } + }); } - protected override Task LeaveRoomInternal() - { - if (!IsConnected.Value) - return Task.FromCanceled(new CancellationToken(true)); + private readonly TaskChain joinOrLeaveTaskChain = new TaskChain(); + private CancellationTokenSource? joinCancellationSource; - return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); + /// + /// Joins the for a given API . + /// + /// The API . + public async Task JoinRoom(Room room) + { + var cancellationSource = joinCancellationSource = new CancellationTokenSource(); + + await joinOrLeaveTaskChain.Add(async () => + { + if (Room != null) + throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + + Debug.Assert(room.RoomID.Value != null); + + // Join the server-side room. + var joinedRoom = await JoinRoom(room.RoomID.Value.Value).ConfigureAwait(false); + Debug.Assert(joinedRoom != null); + + // Populate users. + Debug.Assert(joinedRoom.Users != null); + await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); + + // Update the stored room (must be done on update thread for thread-safety). + await scheduleAsync(() => + { + Room = joinedRoom; + apiRoom = room; + foreach (var user in joinedRoom.Users) + updateUserPlayingState(user.UserID, user.State); + }, cancellationSource.Token).ConfigureAwait(false); + + // Update room settings. + await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token).ConfigureAwait(false); + }, cancellationSource.Token).ConfigureAwait(false); } - public override Task TransferHost(int userId) + /// + /// Joins the with a given ID. + /// + /// The room ID. + /// The joined . + protected abstract Task JoinRoom(long roomId); + + public Task LeaveRoom() { - if (!IsConnected.Value) + // The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled. + // This includes the setting of Room itself along with the initial update of the room settings on join. + joinCancellationSource?.Cancel(); + + // Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background. + // However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed. + // For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time. + var scheduledReset = scheduleAsync(() => + { + apiRoom = null; + Room = null; + CurrentMatchPlayingUserIds.Clear(); + + RoomUpdated?.Invoke(); + }); + + return joinOrLeaveTaskChain.Add(async () => + { + await scheduledReset.ConfigureAwait(false); + await LeaveRoomInternal().ConfigureAwait(false); + }); + } + + protected abstract Task LeaveRoomInternal(); + + /// + /// Change the current settings. + /// + /// + /// A room must be joined for this to have any effect. + /// + /// The new room name, if any. + /// The new room playlist item, if any. + public Task ChangeSettings(Optional name = default, Optional item = default) + { + if (Room == null) + throw new InvalidOperationException("Must be joined to a match to change settings."); + + // A dummy playlist item filled with the current room settings (except mods). + var existingPlaylistItem = new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + OnlineBeatmapID = Room.Settings.BeatmapID, + MD5Hash = Room.Settings.BeatmapChecksum + } + }, + RulesetID = Room.Settings.RulesetID + }; + + return ChangeSettings(new MultiplayerRoomSettings + { + Name = name.GetOr(Room.Settings.Name), + BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, + BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, + RulesetID = item.GetOr(existingPlaylistItem).RulesetID, + RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, + AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, + }); + } + + /// + /// Toggles the 's ready state. + /// + /// If a toggle of ready state is not valid at this time. + public async Task ToggleReady() + { + var localUser = LocalUser; + + if (localUser == null) + return; + + switch (localUser.State) + { + case MultiplayerUserState.Idle: + await ChangeState(MultiplayerUserState.Ready).ConfigureAwait(false); + return; + + case MultiplayerUserState.Ready: + await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false); + return; + + default: + throw new InvalidOperationException($"Cannot toggle ready when in {localUser.State}"); + } + } + + /// + /// Toggles the 's spectating state. + /// + /// If a toggle of the spectating state is not valid at this time. + public async Task ToggleSpectate() + { + var localUser = LocalUser; + + if (localUser == null) + return; + + switch (localUser.State) + { + case MultiplayerUserState.Idle: + case MultiplayerUserState.Ready: + await ChangeState(MultiplayerUserState.Spectating).ConfigureAwait(false); + return; + + case MultiplayerUserState.Spectating: + await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false); + return; + + default: + throw new InvalidOperationException($"Cannot toggle spectate when in {localUser.State}"); + } + } + + public abstract Task TransferHost(int userId); + + public abstract Task ChangeSettings(MultiplayerRoomSettings settings); + + public abstract Task ChangeState(MultiplayerUserState newState); + + public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + + /// + /// Change the local user's mods in the currently joined room. + /// + /// The proposed new mods, excluding any required by the room itself. + public Task ChangeUserMods(IEnumerable newMods) => ChangeUserMods(newMods.Select(m => new APIMod(m)).ToList()); + + public abstract Task ChangeUserMods(IEnumerable newMods); + + public abstract Task StartMatch(); + + Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) + { + if (Room == null) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId); + Scheduler.Add(() => + { + if (Room == null) + return; + + Debug.Assert(apiRoom != null); + + Room.State = state; + + switch (state) + { + case MultiplayerRoomState.Open: + apiRoom.Status.Value = new RoomStatusOpen(); + break; + + case MultiplayerRoomState.Playing: + apiRoom.Status.Value = new RoomStatusPlaying(); + break; + + case MultiplayerRoomState.Closed: + apiRoom.Status.Value = new RoomStatusEnded(); + break; + } + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; } - public override Task ChangeSettings(MultiplayerRoomSettings settings) + async Task IMultiplayerClient.UserJoined(MultiplayerRoomUser user) { - if (!IsConnected.Value) + if (Room == null) + return; + + await PopulateUser(user).ConfigureAwait(false); + + Scheduler.Add(() => + { + if (Room == null) + return; + + // for sanity, ensure that there can be no duplicate users in the room user list. + if (Room.Users.Any(existing => existing.UserID == user.UserID)) + return; + + Room.Users.Add(user); + + RoomUpdated?.Invoke(); + }, false); + } + + Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) + { + if (Room == null) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeSettings), settings); + Scheduler.Add(() => + { + if (Room == null) + return; + + Room.Users.Remove(user); + CurrentMatchPlayingUserIds.Remove(user.UserID); + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; } - public override Task ChangeState(MultiplayerUserState newState) + Task IMultiplayerClient.HostChanged(int userId) { - if (!IsConnected.Value) + if (Room == null) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); + Scheduler.Add(() => + { + if (Room == null) + return; + + Debug.Assert(apiRoom != null); + + var user = Room.Users.FirstOrDefault(u => u.UserID == userId); + + Room.Host = user; + apiRoom.Host.Value = user?.User; + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; } - public override Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability) + Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) { - if (!IsConnected.Value) + updateLocalRoomSettings(newSettings); + return Task.CompletedTask; + } + + Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state) + { + if (Room == null) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability); + Scheduler.Add(() => + { + if (Room == null) + return; + + Room.Users.Single(u => u.UserID == userId).State = state; + + updateUserPlayingState(userId, state); + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; } - public override Task ChangeUserMods(IEnumerable newMods) + Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability) { - if (!IsConnected.Value) + if (Room == null) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods); + Scheduler.Add(() => + { + var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); + + // errors here are not critical - beatmap availability state is mostly for display. + if (user == null) + return; + + user.BeatmapAvailability = beatmapAvailability; + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; } - public override Task StartMatch() + public Task UserModsChanged(int userId, IEnumerable mods) { - if (!IsConnected.Value) + if (Room == null) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); + Scheduler.Add(() => + { + var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); + + // errors here are not critical - user mods are mostly for display. + if (user == null) + return; + + user.Mods = mods; + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; } - protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) + Task IMultiplayerClient.LoadRequested() { - var tcs = new TaskCompletionSource(); - var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); + if (Room == null) + return Task.CompletedTask; - req.Success += res => + Scheduler.Add(() => + { + if (Room == null) + return; + + LoadRequested?.Invoke(); + }, false); + + return Task.CompletedTask; + } + + Task IMultiplayerClient.MatchStarted() + { + if (Room == null) + return Task.CompletedTask; + + Scheduler.Add(() => + { + if (Room == null) + return; + + MatchStarted?.Invoke(); + }, false); + + return Task.CompletedTask; + } + + Task IMultiplayerClient.ResultsReady() + { + if (Room == null) + return Task.CompletedTask; + + Scheduler.Add(() => + { + if (Room == null) + return; + + ResultsReady?.Invoke(); + }, false); + + return Task.CompletedTask; + } + + /// + /// Populates the for a given . + /// + /// The to populate. + protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID).ConfigureAwait(false); + + /// + /// Updates the local room settings with the given . + /// + /// + /// This updates both the joined and the respective API . + /// + /// The new to update from. + /// The to cancel the update. + private Task updateLocalRoomSettings(MultiplayerRoomSettings settings, CancellationToken cancellationToken = default) => scheduleAsync(() => + { + if (Room == null) + return; + + Debug.Assert(apiRoom != null); + + // Update a few properties of the room instantaneously. + Room.Settings = settings; + apiRoom.Name.Value = Room.Settings.Name; + + // The current item update is delayed until an online beatmap lookup (below) succeeds. + // In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here. + CurrentMatchPlayingItem.Value = null; + + RoomUpdated?.Invoke(); + + GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() => + { + if (cancellationToken.IsCancellationRequested) + return; + + updatePlaylist(settings, set.Result); + }), TaskContinuationOptions.OnlyOnRanToCompletion); + }, cancellationToken); + + private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet) + { + if (Room == null || !Room.Settings.Equals(settings)) + return; + + Debug.Assert(apiRoom != null); + + var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID); + beatmap.MD5Hash = settings.BeatmapChecksum; + + var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance(); + var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); + var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); + + // Try to retrieve the existing playlist item from the API room. + var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); + + if (playlistItem != null) + updateItem(playlistItem); + else + { + // An existing playlist item does not exist, so append a new one. + updateItem(playlistItem = new PlaylistItem()); + apiRoom.Playlist.Add(playlistItem); + } + + CurrentMatchPlayingItem.Value = playlistItem; + + void updateItem(PlaylistItem item) + { + item.ID = settings.PlaylistItemId; + item.Beatmap.Value = beatmap; + item.Ruleset.Value = ruleset.RulesetInfo; + item.RequiredMods.Clear(); + item.RequiredMods.AddRange(mods); + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(allowedMods); + } + } + + /// + /// Retrieves a from an online source. + /// + /// The beatmap set ID. + /// A token to cancel the request. + /// The retrieval task. + protected abstract Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default); + + /// + /// For the provided user ID, update whether the user is included in . + /// + /// The user's ID. + /// The new state of the user. + private void updateUserPlayingState(int userId, MultiplayerUserState state) + { + bool wasPlaying = CurrentMatchPlayingUserIds.Contains(userId); + bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; + + if (isPlaying == wasPlaying) + return; + + if (isPlaying) + CurrentMatchPlayingUserIds.Add(userId); + else + CurrentMatchPlayingUserIds.Remove(userId); + } + + private Task scheduleAsync(Action action, CancellationToken cancellationToken = default) + { + var tcs = new TaskCompletionSource(); + + Scheduler.Add(() => { if (cancellationToken.IsCancellationRequested) { @@ -136,20 +625,18 @@ namespace osu.Game.Online.Multiplayer return; } - tcs.SetResult(res.ToBeatmapSet(Rulesets)); - }; - - req.Failure += e => tcs.SetException(e); - - API.Queue(req); + try + { + action(); + tcs.SetResult(true); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + }); return tcs.Task; } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - connector?.Dispose(); - } } } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs new file mode 100644 index 0000000000..cf1e18e059 --- /dev/null +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -0,0 +1,158 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Rooms; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// A with online connectivity. + /// + public class OnlineMultiplayerClient : MultiplayerClient + { + private readonly string endpoint; + + private IHubClientConnector? connector; + + public override IBindable IsConnected { get; } = new BindableBool(); + + private HubConnection? connection => connector?.CurrentConnection; + + public OnlineMultiplayerClient(EndpointConfiguration endpoints) + { + endpoint = endpoints.MultiplayerEndpointUrl; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + connector = api.GetHubConnector(nameof(OnlineMultiplayerClient), endpoint); + + if (connector != null) + { + connector.ConfigureConnection = connection => + { + // this is kind of SILLY + // https://github.com/dotnet/aspnetcore/issues/15198 + connection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); + connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); + connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); + connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); + connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); + connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); + connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); + connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); + connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); + connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); + connection.On(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged); + }; + + IsConnected.BindTo(connector.IsConnected); + } + } + + protected override Task JoinRoom(long roomId) + { + if (!IsConnected.Value) + return Task.FromCanceled(new CancellationToken(true)); + + return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoom), roomId); + } + + protected override Task LeaveRoomInternal() + { + if (!IsConnected.Value) + return Task.FromCanceled(new CancellationToken(true)); + + return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); + } + + public override Task TransferHost(int userId) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId); + } + + public override Task ChangeSettings(MultiplayerRoomSettings settings) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeSettings), settings); + } + + public override Task ChangeState(MultiplayerUserState newState) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); + } + + public override Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability); + } + + public override Task ChangeUserMods(IEnumerable newMods) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods); + } + + public override Task StartMatch() + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); + } + + protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) + { + var tcs = new TaskCompletionSource(); + var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); + + req.Success += res => + { + if (cancellationToken.IsCancellationRequested) + { + tcs.SetCanceled(); + return; + } + + tcs.SetResult(res.ToBeatmapSet(Rulesets)); + }; + + req.Failure += e => tcs.SetException(e); + + API.Queue(req); + + return tcs.Task; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + connector?.Dispose(); + } + } +} diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs deleted file mode 100644 index 7fe48d54b1..0000000000 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ /dev/null @@ -1,642 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics; -using osu.Framework.Logging; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Online.API; -using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Users; -using osu.Game.Utils; - -namespace osu.Game.Online.Multiplayer -{ - public abstract class StatefulMultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer - { - /// - /// Invoked when any change occurs to the multiplayer room. - /// - public event Action? RoomUpdated; - - /// - /// Invoked when the multiplayer server requests the current beatmap to be loaded into play. - /// - public event Action? LoadRequested; - - /// - /// Invoked when the multiplayer server requests gameplay to be started. - /// - public event Action? MatchStarted; - - /// - /// Invoked when the multiplayer server has finished collating results. - /// - public event Action? ResultsReady; - - /// - /// Whether the is currently connected. - /// This is NOT thread safe and usage should be scheduled. - /// - public abstract IBindable IsConnected { get; } - - /// - /// The joined . - /// - public MultiplayerRoom? Room { get; private set; } - - /// - /// The users in the joined which are participating in the current gameplay loop. - /// - public readonly BindableList CurrentMatchPlayingUserIds = new BindableList(); - - public readonly Bindable CurrentMatchPlayingItem = new Bindable(); - - /// - /// The corresponding to the local player, if available. - /// - public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id); - - /// - /// Whether the is the host in . - /// - public bool IsHost - { - get - { - var localUser = LocalUser; - return localUser != null && Room?.Host != null && localUser.Equals(Room.Host); - } - } - - [Resolved] - protected IAPIProvider API { get; private set; } = null!; - - [Resolved] - protected RulesetStore Rulesets { get; private set; } = null!; - - [Resolved] - private UserLookupCache userLookupCache { get; set; } = null!; - - private Room? apiRoom; - - [BackgroundDependencyLoader] - private void load() - { - IsConnected.BindValueChanged(connected => - { - // clean up local room state on server disconnect. - if (!connected.NewValue && Room != null) - { - Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); - LeaveRoom(); - } - }); - } - - private readonly TaskChain joinOrLeaveTaskChain = new TaskChain(); - private CancellationTokenSource? joinCancellationSource; - - /// - /// Joins the for a given API . - /// - /// The API . - public async Task JoinRoom(Room room) - { - var cancellationSource = joinCancellationSource = new CancellationTokenSource(); - - await joinOrLeaveTaskChain.Add(async () => - { - if (Room != null) - throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); - - Debug.Assert(room.RoomID.Value != null); - - // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value).ConfigureAwait(false); - Debug.Assert(joinedRoom != null); - - // Populate users. - Debug.Assert(joinedRoom.Users != null); - await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); - - // Update the stored room (must be done on update thread for thread-safety). - await scheduleAsync(() => - { - Room = joinedRoom; - apiRoom = room; - foreach (var user in joinedRoom.Users) - updateUserPlayingState(user.UserID, user.State); - }, cancellationSource.Token).ConfigureAwait(false); - - // Update room settings. - await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token).ConfigureAwait(false); - }, cancellationSource.Token).ConfigureAwait(false); - } - - /// - /// Joins the with a given ID. - /// - /// The room ID. - /// The joined . - protected abstract Task JoinRoom(long roomId); - - public Task LeaveRoom() - { - // The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled. - // This includes the setting of Room itself along with the initial update of the room settings on join. - joinCancellationSource?.Cancel(); - - // Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background. - // However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed. - // For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time. - var scheduledReset = scheduleAsync(() => - { - apiRoom = null; - Room = null; - CurrentMatchPlayingUserIds.Clear(); - - RoomUpdated?.Invoke(); - }); - - return joinOrLeaveTaskChain.Add(async () => - { - await scheduledReset.ConfigureAwait(false); - await LeaveRoomInternal().ConfigureAwait(false); - }); - } - - protected abstract Task LeaveRoomInternal(); - - /// - /// Change the current settings. - /// - /// - /// A room must be joined for this to have any effect. - /// - /// The new room name, if any. - /// The new room playlist item, if any. - public Task ChangeSettings(Optional name = default, Optional item = default) - { - if (Room == null) - throw new InvalidOperationException("Must be joined to a match to change settings."); - - // A dummy playlist item filled with the current room settings (except mods). - var existingPlaylistItem = new PlaylistItem - { - Beatmap = - { - Value = new BeatmapInfo - { - OnlineBeatmapID = Room.Settings.BeatmapID, - MD5Hash = Room.Settings.BeatmapChecksum - } - }, - RulesetID = Room.Settings.RulesetID - }; - - return ChangeSettings(new MultiplayerRoomSettings - { - Name = name.GetOr(Room.Settings.Name), - BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, - BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, - RulesetID = item.GetOr(existingPlaylistItem).RulesetID, - RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, - AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, - }); - } - - /// - /// Toggles the 's ready state. - /// - /// If a toggle of ready state is not valid at this time. - public async Task ToggleReady() - { - var localUser = LocalUser; - - if (localUser == null) - return; - - switch (localUser.State) - { - case MultiplayerUserState.Idle: - await ChangeState(MultiplayerUserState.Ready).ConfigureAwait(false); - return; - - case MultiplayerUserState.Ready: - await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false); - return; - - default: - throw new InvalidOperationException($"Cannot toggle ready when in {localUser.State}"); - } - } - - /// - /// Toggles the 's spectating state. - /// - /// If a toggle of the spectating state is not valid at this time. - public async Task ToggleSpectate() - { - var localUser = LocalUser; - - if (localUser == null) - return; - - switch (localUser.State) - { - case MultiplayerUserState.Idle: - case MultiplayerUserState.Ready: - await ChangeState(MultiplayerUserState.Spectating).ConfigureAwait(false); - return; - - case MultiplayerUserState.Spectating: - await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false); - return; - - default: - throw new InvalidOperationException($"Cannot toggle spectate when in {localUser.State}"); - } - } - - public abstract Task TransferHost(int userId); - - public abstract Task ChangeSettings(MultiplayerRoomSettings settings); - - public abstract Task ChangeState(MultiplayerUserState newState); - - public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); - - /// - /// Change the local user's mods in the currently joined room. - /// - /// The proposed new mods, excluding any required by the room itself. - public Task ChangeUserMods(IEnumerable newMods) => ChangeUserMods(newMods.Select(m => new APIMod(m)).ToList()); - - public abstract Task ChangeUserMods(IEnumerable newMods); - - public abstract Task StartMatch(); - - Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - if (Room == null) - return; - - Debug.Assert(apiRoom != null); - - Room.State = state; - - switch (state) - { - case MultiplayerRoomState.Open: - apiRoom.Status.Value = new RoomStatusOpen(); - break; - - case MultiplayerRoomState.Playing: - apiRoom.Status.Value = new RoomStatusPlaying(); - break; - - case MultiplayerRoomState.Closed: - apiRoom.Status.Value = new RoomStatusEnded(); - break; - } - - RoomUpdated?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - async Task IMultiplayerClient.UserJoined(MultiplayerRoomUser user) - { - if (Room == null) - return; - - await PopulateUser(user).ConfigureAwait(false); - - Scheduler.Add(() => - { - if (Room == null) - return; - - // for sanity, ensure that there can be no duplicate users in the room user list. - if (Room.Users.Any(existing => existing.UserID == user.UserID)) - return; - - Room.Users.Add(user); - - RoomUpdated?.Invoke(); - }, false); - } - - Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - if (Room == null) - return; - - Room.Users.Remove(user); - CurrentMatchPlayingUserIds.Remove(user.UserID); - - RoomUpdated?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - Task IMultiplayerClient.HostChanged(int userId) - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - if (Room == null) - return; - - Debug.Assert(apiRoom != null); - - var user = Room.Users.FirstOrDefault(u => u.UserID == userId); - - Room.Host = user; - apiRoom.Host.Value = user?.User; - - RoomUpdated?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) - { - updateLocalRoomSettings(newSettings); - return Task.CompletedTask; - } - - Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state) - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - if (Room == null) - return; - - Room.Users.Single(u => u.UserID == userId).State = state; - - updateUserPlayingState(userId, state); - - RoomUpdated?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability) - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); - - // errors here are not critical - beatmap availability state is mostly for display. - if (user == null) - return; - - user.BeatmapAvailability = beatmapAvailability; - - RoomUpdated?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - public Task UserModsChanged(int userId, IEnumerable mods) - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); - - // errors here are not critical - user mods are mostly for display. - if (user == null) - return; - - user.Mods = mods; - - RoomUpdated?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - Task IMultiplayerClient.LoadRequested() - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - if (Room == null) - return; - - LoadRequested?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - Task IMultiplayerClient.MatchStarted() - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - if (Room == null) - return; - - MatchStarted?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - Task IMultiplayerClient.ResultsReady() - { - if (Room == null) - return Task.CompletedTask; - - Scheduler.Add(() => - { - if (Room == null) - return; - - ResultsReady?.Invoke(); - }, false); - - return Task.CompletedTask; - } - - /// - /// Populates the for a given . - /// - /// The to populate. - protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID).ConfigureAwait(false); - - /// - /// Updates the local room settings with the given . - /// - /// - /// This updates both the joined and the respective API . - /// - /// The new to update from. - /// The to cancel the update. - private Task updateLocalRoomSettings(MultiplayerRoomSettings settings, CancellationToken cancellationToken = default) => scheduleAsync(() => - { - if (Room == null) - return; - - Debug.Assert(apiRoom != null); - - // Update a few properties of the room instantaneously. - Room.Settings = settings; - apiRoom.Name.Value = Room.Settings.Name; - - // The current item update is delayed until an online beatmap lookup (below) succeeds. - // In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here. - CurrentMatchPlayingItem.Value = null; - - RoomUpdated?.Invoke(); - - GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() => - { - if (cancellationToken.IsCancellationRequested) - return; - - updatePlaylist(settings, set.Result); - }), TaskContinuationOptions.OnlyOnRanToCompletion); - }, cancellationToken); - - private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet) - { - if (Room == null || !Room.Settings.Equals(settings)) - return; - - Debug.Assert(apiRoom != null); - - var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID); - beatmap.MD5Hash = settings.BeatmapChecksum; - - var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance(); - var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); - var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); - - // Try to retrieve the existing playlist item from the API room. - var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); - - if (playlistItem != null) - updateItem(playlistItem); - else - { - // An existing playlist item does not exist, so append a new one. - updateItem(playlistItem = new PlaylistItem()); - apiRoom.Playlist.Add(playlistItem); - } - - CurrentMatchPlayingItem.Value = playlistItem; - - void updateItem(PlaylistItem item) - { - item.ID = settings.PlaylistItemId; - item.Beatmap.Value = beatmap; - item.Ruleset.Value = ruleset.RulesetInfo; - item.RequiredMods.Clear(); - item.RequiredMods.AddRange(mods); - item.AllowedMods.Clear(); - item.AllowedMods.AddRange(allowedMods); - } - } - - /// - /// Retrieves a from an online source. - /// - /// The beatmap set ID. - /// A token to cancel the request. - /// The retrieval task. - protected abstract Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default); - - /// - /// For the provided user ID, update whether the user is included in . - /// - /// The user's ID. - /// The new state of the user. - private void updateUserPlayingState(int userId, MultiplayerUserState state) - { - bool wasPlaying = CurrentMatchPlayingUserIds.Contains(userId); - bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; - - if (isPlaying == wasPlaying) - return; - - if (isPlaying) - CurrentMatchPlayingUserIds.Add(userId); - else - CurrentMatchPlayingUserIds.Remove(userId); - } - - private Task scheduleAsync(Action action, CancellationToken cancellationToken = default) - { - var tcs = new TaskCompletionSource(); - - Scheduler.Add(() => - { - if (cancellationToken.IsCancellationRequested) - { - tcs.SetCanceled(); - return; - } - - try - { - action(); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }); - - return tcs.Task; - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fbe4022cc1..656d6319b4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -86,7 +86,7 @@ namespace osu.Game protected IAPIProvider API; private SpectatorStreamingClient spectatorStreaming; - private StatefulMultiplayerClient multiplayerClient; + private MultiplayerClient multiplayerClient; protected MenuCursorContainer MenuCursorContainer; @@ -241,7 +241,7 @@ namespace osu.Game dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash)); dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient(endpoints)); - dependencies.CacheAs(multiplayerClient = new MultiplayerClient(endpoints)); + dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs index a13d2cf540..cc51b5b691 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private IBindable operationInProgress; [Resolved] - private StatefulMultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3199232f6f..fe9979b161 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private IRoomManager manager { get; set; } [Resolved] - private StatefulMultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } [Resolved] private Bindable currentRoom { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 085c824bdc..a065d04f64 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public class Multiplayer : OnlinePlayScreen { [Resolved] - private StatefulMultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } public override void OnResuming(IScreen last) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 0a9a3f680f..4d20652465 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room); [Resolved] - private StatefulMultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } public override void Open(Room room) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index c9f0f6de90..3733b85a5e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public class MultiplayerMatchSongSelect : OnlinePlaySongSelect { [Resolved] - private StatefulMultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } private LoadingLayer loadingLayer; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 783b8b4bf2..62ef70ed68 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override string ShortTitle => "room"; [Resolved] - private StatefulMultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index ae2042fbe8..1bbe49a705 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override bool CheckModsAllowFailure() => false; [Resolved] - private StatefulMultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } private IBindable isConnected; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs index 8030107ad8..d334c618f5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected MultiplayerRoom Room => Client.Room; [Resolved] - protected StatefulMultiplayerClient Client { get; private set; } + protected MultiplayerClient Client { get; private set; } protected override void LoadComplete() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index 1e57847f04..8526196902 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public class MultiplayerRoomManager : RoomManager { [Resolved] - private StatefulMultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } public readonly Bindable TimeBetweenListingPolls = new Bindable(); public readonly Bindable TimeBetweenSelectionPolls = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsListHeader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsListHeader.cs index 6c1a55a0eb..7e442c6568 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsListHeader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsListHeader.cs @@ -10,7 +10,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants public class ParticipantsListHeader : OverlinedHeader { [Resolved] - private StatefulMultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } public ParticipantsListHeader() : base("Participants") diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 8c7b7bab01..a0245a1e59 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private SpectatorStreamingClient spectatorClient { get; set; } [Resolved] - private StatefulMultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } private readonly PlayerArea[] instances; private MasterGameplayClockContainer masterClockContainer; diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 70de067784..bbb3c5ebb2 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play.HUD private SpectatorStreamingClient streamingClient { get; set; } [Resolved] - private StatefulMultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } [Resolved] private UserLookupCache userLookupCache { get; set; } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index db344b28dd..c76d1053b2 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - [Cached(typeof(StatefulMultiplayerClient))] + [Cached(typeof(MultiplayerClient))] public TestMultiplayerClient Client { get; } [Cached(typeof(IRoomManager))] diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 167cf705a7..b12bd8091d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -20,7 +20,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestMultiplayerClient : StatefulMultiplayerClient + public class TestMultiplayerClient : MultiplayerClient { public override IBindable IsConnected => isConnected; private readonly Bindable isConnected = new Bindable(true); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index e57411d04d..1abf4d8f5d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override Container Content => content; private readonly Container content; - [Cached(typeof(StatefulMultiplayerClient))] + [Cached(typeof(MultiplayerClient))] public readonly TestMultiplayerClient Client; [Cached(typeof(IRoomManager))] From 6beeb7f7c433d636e30cc46f56d03b585c71d647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 15:55:07 +0900 Subject: [PATCH 224/304] Rename SpectatorStreamingClient -> SpectatorClient --- .../Visual/Gameplay/TestSceneSpectator.cs | 14 ++++++------- .../Gameplay/TestSceneSpectatorPlayback.cs | 14 ++++++------- .../TestSceneMultiSpectatorLeaderboard.cs | 14 ++++++------- .../TestSceneMultiSpectatorScreen.cs | 20 +++++++++---------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 18 ++++++++--------- .../TestSceneCurrentlyPlayingDisplay.cs | 14 ++++++------- ...rStreamingClient.cs => SpectatorClient.cs} | 6 +++--- osu.Game/OsuGameBase.cs | 6 +++--- .../Dashboard/CurrentlyPlayingDisplay.cs | 4 ++-- osu.Game/Rulesets/UI/ReplayRecorder.cs | 8 ++++---- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../HUD/MultiplayerGameplayLeaderboard.cs | 14 ++++++------- osu.Game/Screens/Play/SpectatorPlayer.cs | 10 +++++----- .../Screens/Play/SpectatorResultsScreen.cs | 8 ++++---- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- ...eamingClient.cs => TestSpectatorClient.cs} | 4 ++-- 16 files changed, 79 insertions(+), 79 deletions(-) rename osu.Game/Online/Spectator/{SpectatorStreamingClient.cs => SpectatorClient.cs} (97%) rename osu.Game/Tests/Visual/Spectator/{TestSpectatorStreamingClient.cs => TestSpectatorClient.cs} (96%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index a7ed217b4d..56a4ab8cba 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -27,8 +27,8 @@ namespace osu.Game.Tests.Visual.Gameplay { private readonly User streamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" }; - [Cached(typeof(SpectatorStreamingClient))] - private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); + [Cached(typeof(SpectatorClient))] + private TestSpectatorClient testSpectatorClient = new TestSpectatorClient(); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("add streaming client", () => { - Remove(testSpectatorStreamingClient); - Add(testSpectatorStreamingClient); + Remove(testSpectatorClient); + Add(testSpectatorClient); }); finish(); @@ -212,9 +212,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player); - private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); + private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorStreamingClient.EndPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); + private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorClient.EndPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("send frames", () => { - testSpectatorStreamingClient.SendFrames(streamingUser.Id, nextFrame, count); + testSpectatorClient.SendFrames(streamingUser.Id, nextFrame, count); nextFrame += count; }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 9c763814f3..469f594fdc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay private IAPIProvider api { get; set; } [Resolved] - private SpectatorStreamingClient streamingClient { get; set; } + private SpectatorClient spectatorClient { get; set; } [Cached] private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - users.BindTo(streamingClient.PlayingUsers); + users.BindTo(spectatorClient.PlayingUsers); users.BindCollectionChanged((obj, args) => { switch (args.Action) @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (int user in args.NewItems) { if (user == api.LocalUser.Value.Id) - streamingClient.WatchUser(user); + spectatorClient.WatchUser(user); } break; @@ -91,14 +91,14 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (int user in args.OldItems) { if (user == api.LocalUser.Value.Id) - streamingClient.StopWatchingUser(user); + spectatorClient.StopWatchingUser(user); } break; } }, true); - streamingClient.OnNewFrames += onNewFrames; + spectatorClient.OnNewFrames += onNewFrames; Add(new GridContainer { @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - private double latency = SpectatorStreamingClient.TIME_BETWEEN_SENDS; + private double latency = SpectatorClient.TIME_BETWEEN_SENDS; protected override void Update() { @@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("stop recorder", () => { recorder.Expire(); - streamingClient.OnNewFrames -= onNewFrames; + spectatorClient.OnNewFrames -= onNewFrames; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 263adc07e1..afd4401a63 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -23,8 +23,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - [Cached(typeof(SpectatorStreamingClient))] - private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient(); + [Cached(typeof(SpectatorClient))] + private TestSpectatorClient spectatorClient = new TestSpectatorClient(); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.Content.AddRange(new Drawable[] { - streamingClient, + spectatorClient, lookupCache, content = new Container { RelativeSizeAxes = Axes.Both } }); @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (var (userId, clock) in clocks) { - streamingClient.EndPlay(userId, 0); + spectatorClient.EndPlay(userId, 0); clock.CurrentTime = 0; } }); @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create leaderboard", () => { foreach (var (userId, _) in clocks) - streamingClient.StartPlay(userId, 0); + spectatorClient.StartPlay(userId, 0); Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); @@ -96,10 +96,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - streamingClient.SendFrames(PLAYER_1_ID, i, 1); + spectatorClient.SendFrames(PLAYER_1_ID, i, 1); if (i % 10 == 0) - streamingClient.SendFrames(PLAYER_2_ID, i, 10); + spectatorClient.SendFrames(PLAYER_2_ID, i, 10); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 689c249d05..23095a1ea8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -22,8 +22,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { - [Cached(typeof(SpectatorStreamingClient))] - private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient(); + [Cached(typeof(SpectatorClient))] + private TestSpectatorClient spectatorClient = new TestSpectatorClient(); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -59,14 +59,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add streaming client", () => { - Remove(streamingClient); - Add(streamingClient); + Remove(spectatorClient); + Add(spectatorClient); }); AddStep("finish previous gameplay", () => { foreach (var id in playingUserIds) - streamingClient.EndPlay(id, importedBeatmapId); + spectatorClient.EndPlay(id, importedBeatmapId); playingUserIds.Clear(); }); } @@ -87,11 +87,11 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player first_player_id", () => streamingClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); + AddStep("load player first_player_id", () => spectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player second_player_id", () => streamingClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); + AddStep("load player second_player_id", () => spectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } @@ -251,7 +251,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int id in userIds) { Client.CurrentMatchPlayingUserIds.Add(id); - streamingClient.StartPlay(id, beatmapId ?? importedBeatmapId); + spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); nextFrame[id] = 0; } @@ -262,7 +262,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("end play", () => { - streamingClient.EndPlay(userId, beatmapId ?? importedBeatmapId); + spectatorClient.EndPlay(userId, beatmapId ?? importedBeatmapId); playingUserIds.Remove(userId); nextFrame.Remove(userId); }); @@ -276,7 +276,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (int id in userIds) { - streamingClient.SendFrames(id, nextFrame[id], count); + spectatorClient.SendFrames(id, nextFrame[id], count); nextFrame[id] += count; } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6813a6e7dd..80b9aa8228 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -28,8 +28,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { private const int users = 16; - [Cached(typeof(SpectatorStreamingClient))] - private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(); + [Cached(typeof(SpectatorClient))] + private TestMultiplayerSpectatorClient spectatorClient = new TestMultiplayerSpectatorClient(); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.Content.Children = new Drawable[] { - streamingClient, + spectatorClient, lookupCache, Content }; @@ -71,10 +71,10 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); for (int i = 0; i < users; i++) - streamingClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers); + Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers); Children = new Drawable[] { @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, streamingClient.PlayingUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, spectatorClient.PlayingUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestScoreUpdates() { - AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100); + AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 100); AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded); } @@ -109,12 +109,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestChangeScoringMode() { - AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 5); + AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 5); AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic)); AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - public class TestMultiplayerStreaming : TestSpectatorStreamingClient + public class TestMultiplayerSpectatorClient : TestSpectatorClient { private readonly Dictionary lastHeaders = new Dictionary(); diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index 8ae6398003..9bc0b32eee 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -19,8 +19,8 @@ namespace osu.Game.Tests.Visual.Online { public class TestSceneCurrentlyPlayingDisplay : OsuTestScene { - [Cached(typeof(SpectatorStreamingClient))] - private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); + [Cached(typeof(SpectatorClient))] + private TestSpectatorClient testSpectatorClient = new TestSpectatorClient(); private CurrentlyPlayingDisplay currentlyPlaying; @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("add streaming client", () => { - nestedContainer?.Remove(testSpectatorStreamingClient); + nestedContainer?.Remove(testSpectatorClient); Remove(lookupCache); Children = new Drawable[] @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - testSpectatorStreamingClient, + testSpectatorClient, currentlyPlaying = new CurrentlyPlayingDisplay { RelativeSizeAxes = Axes.Both, @@ -55,15 +55,15 @@ namespace osu.Game.Tests.Visual.Online }; }); - AddStep("Reset players", () => testSpectatorStreamingClient.PlayingUsers.Clear()); + AddStep("Reset players", () => testSpectatorClient.PlayingUsers.Clear()); } [Test] public void TestBasicDisplay() { - AddStep("Add playing user", () => testSpectatorStreamingClient.PlayingUsers.Add(2)); + AddStep("Add playing user", () => testSpectatorClient.PlayingUsers.Add(2)); AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType()?.FirstOrDefault()?.User.Id == 2); - AddStep("Remove playing user", () => testSpectatorStreamingClient.PlayingUsers.Remove(2)); + AddStep("Remove playing user", () => testSpectatorClient.PlayingUsers.Remove(2)); AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType().Any()); } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs similarity index 97% rename from osu.Game/Online/Spectator/SpectatorStreamingClient.cs rename to osu.Game/Online/Spectator/SpectatorClient.cs index ec6d1bf9d8..43115d577c 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -23,7 +23,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Online.Spectator { - public class SpectatorStreamingClient : Component, ISpectatorClient + public class SpectatorClient : Component, ISpectatorClient { /// /// The maximum milliseconds between frame bundle sends. @@ -80,7 +80,7 @@ namespace osu.Game.Online.Spectator /// public event Action OnUserFinishedPlaying; - public SpectatorStreamingClient(EndpointConfiguration endpoints) + public SpectatorClient(EndpointConfiguration endpoints) { endpoint = endpoints.SpectatorEndpointUrl; } @@ -88,7 +88,7 @@ namespace osu.Game.Online.Spectator [BackgroundDependencyLoader] private void load(IAPIProvider api) { - connector = api.GetHubConnector(nameof(SpectatorStreamingClient), endpoint); + connector = api.GetHubConnector(nameof(SpectatorClient), endpoint); if (connector != null) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fbe4022cc1..3707e3b7be 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -85,7 +85,7 @@ namespace osu.Game protected IAPIProvider API; - private SpectatorStreamingClient spectatorStreaming; + private SpectatorClient spectatorClient; private StatefulMultiplayerClient multiplayerClient; protected MenuCursorContainer MenuCursorContainer; @@ -240,7 +240,7 @@ namespace osu.Game dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash)); - dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient(endpoints)); + dependencies.CacheAs(spectatorClient = new SpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new MultiplayerClient(endpoints)); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); @@ -313,7 +313,7 @@ namespace osu.Game // add api components to hierarchy. if (API is APIAccess apiAccess) AddInternal(apiAccess); - AddInternal(spectatorStreaming); + AddInternal(spectatorClient); AddInternal(multiplayerClient); AddInternal(RulesetConfigCache); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 336430fd9b..3051ca7dbe 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Dashboard private FillFlowContainer userFlow; [Resolved] - private SpectatorStreamingClient spectatorStreaming { get; set; } + private SpectatorClient spectatorClient { get; set; } [BackgroundDependencyLoader] private void load() @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - playingUsers.BindTo(spectatorStreaming.PlayingUsers); + playingUsers.BindTo(spectatorClient.PlayingUsers); playingUsers.BindCollectionChanged(onUsersChanged, true); } diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 643ded4cad..d18e0f9541 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.UI public int RecordFrameRate = 60; [Resolved(canBeNull: true)] - private SpectatorStreamingClient spectatorStreaming { get; set; } + private SpectatorClient spectatorClient { get; set; } [Resolved] private GameplayBeatmap gameplayBeatmap { get; set; } @@ -49,13 +49,13 @@ namespace osu.Game.Rulesets.UI inputManager = GetContainingInputManager(); - spectatorStreaming?.BeginPlaying(gameplayBeatmap, target); + spectatorClient?.BeginPlaying(gameplayBeatmap, target); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - spectatorStreaming?.EndPlaying(); + spectatorClient?.EndPlaying(); } protected override void Update() @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.UI { target.Replay.Frames.Add(frame); - spectatorStreaming?.HandleFrame(frame); + spectatorClient?.HandleFrame(frame); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 8c7b7bab01..3ffaeb772a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); [Resolved] - private SpectatorStreamingClient spectatorClient { get; set; } + private SpectatorClient spectatorClient { get; set; } [Resolved] private StatefulMultiplayerClient multiplayerClient { get; set; } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 70de067784..7f59a836c2 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD protected readonly Dictionary UserScores = new Dictionary(); [Resolved] - private SpectatorStreamingClient streamingClient { get; set; } + private SpectatorClient spectatorClient { get; set; } [Resolved] private StatefulMultiplayerClient multiplayerClient { get; set; } @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play.HUD foreach (var userId in playingUsers) { - streamingClient.WatchUser(userId); + spectatorClient.WatchUser(userId); // probably won't be required in the final implementation. var resolvedUser = userLookupCache.GetUserAsync(userId).Result; @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Play.HUD playingUsers.BindCollectionChanged(usersChanged); // this leaderboard should be guaranteed to be completely loaded before the gameplay starts (is a prerequisite in MultiplayerPlayer). - streamingClient.OnNewFrames += handleIncomingFrames; + spectatorClient.OnNewFrames += handleIncomingFrames; } private void usersChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Play.HUD case NotifyCollectionChangedAction.Remove: foreach (var userId in e.OldItems.OfType()) { - streamingClient.StopWatchingUser(userId); + spectatorClient.StopWatchingUser(userId); if (UserScores.TryGetValue(userId, out var trackedData)) trackedData.MarkUserQuit(); @@ -123,14 +123,14 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); - if (streamingClient != null) + if (spectatorClient != null) { foreach (var user in playingUsers) { - streamingClient.StopWatchingUser(user); + spectatorClient.StopWatchingUser(user); } - streamingClient.OnNewFrames -= handleIncomingFrames; + spectatorClient.OnNewFrames -= handleIncomingFrames; } } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 9822f62dd8..a8125dfded 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -31,12 +31,12 @@ namespace osu.Game.Screens.Play } [Resolved] - private SpectatorStreamingClient spectatorStreaming { get; set; } + private SpectatorClient spectatorClient { get; set; } [BackgroundDependencyLoader] private void load() { - spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; + spectatorClient.OnUserBeganPlaying += userBeganPlaying; AddInternal(new OsuSpriteText { @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; + spectatorClient.OnUserBeganPlaying -= userBeganPlaying; return base.OnExiting(next); } @@ -84,8 +84,8 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); - if (spectatorStreaming != null) - spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; + if (spectatorClient != null) + spectatorClient.OnUserBeganPlaying -= userBeganPlaying; } } } diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs index dabdf0a139..fd7af3af85 100644 --- a/osu.Game/Screens/Play/SpectatorResultsScreen.cs +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -17,12 +17,12 @@ namespace osu.Game.Screens.Play } [Resolved] - private SpectatorStreamingClient spectatorStreaming { get; set; } + private SpectatorClient spectatorClient { get; set; } [BackgroundDependencyLoader] private void load() { - spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; + spectatorClient.OnUserBeganPlaying += userBeganPlaying; } private void userBeganPlaying(int userId, SpectatorState state) @@ -40,8 +40,8 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); - if (spectatorStreaming != null) - spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; + if (spectatorClient != null) + spectatorClient.OnUserBeganPlaying -= userBeganPlaying; } } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index bcebd51954..1cf7bc30ee 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Spectate private RulesetStore rulesets { get; set; } [Resolved] - private SpectatorStreamingClient spectatorClient { get; set; } + private SpectatorClient spectatorClient { get; set; } [Resolved] private UserLookupCache userLookupCache { get; set; } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs similarity index 96% rename from osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs rename to osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index cc8437479d..985e293981 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -12,7 +12,7 @@ using osu.Game.Scoring; namespace osu.Game.Tests.Visual.Spectator { - public class TestSpectatorStreamingClient : SpectatorStreamingClient + public class TestSpectatorClient : SpectatorClient { public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; private readonly ConcurrentDictionary watchingUsers = new ConcurrentDictionary(); @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Spectator private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userSentStateDictionary = new Dictionary(); - public TestSpectatorStreamingClient() + public TestSpectatorClient() : base(new DevelopmentEndpointConfiguration()) { } From 3018a41ab5daf24a61671cc1684cc8f089049e8a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 20 May 2021 16:00:49 +0900 Subject: [PATCH 225/304] Remove redundant string interpolation --- osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs b/osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs index 9975c65085..1264d575a4 100644 --- a/osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs @@ -46,11 +46,11 @@ namespace osu.Game.Tests.Gameplay addProxy(new TestDrawableHitObject(5000)); }); - AddStep($"time = 1000", () => clock.CurrentTime = 1000); + AddStep("time = 1000", () => clock.CurrentTime = 1000); AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1); - AddStep($"time = 5000", () => clock.CurrentTime = 5000); + AddStep("time = 5000", () => clock.CurrentTime = 5000); AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1); - AddStep($"time = 6000", () => clock.CurrentTime = 6000); + AddStep("time = 6000", () => clock.CurrentTime = 6000); AddAssert("No proxy is alive", () => proxyContainer.AliveChildren.Count == 0); } From df80531a0a8d6a687b3875628732f67712c12682 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 16:30:56 +0900 Subject: [PATCH 226/304] Split online connectivity into OnlineSpectatorClient --- .../Online/Spectator/OnlineSpectatorClient.cs | 89 +++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 140 +++++++----------- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 142 insertions(+), 89 deletions(-) create mode 100644 osu.Game/Online/Spectator/OnlineSpectatorClient.cs diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs new file mode 100644 index 0000000000..753796158e --- /dev/null +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -0,0 +1,89 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.API; + +namespace osu.Game.Online.Spectator +{ + public class OnlineSpectatorClient : SpectatorClient + { + private readonly string endpoint; + + private IHubClientConnector? connector; + + public override IBindable IsConnected { get; } = new BindableBool(); + + private HubConnection? connection => connector?.CurrentConnection; + + public OnlineSpectatorClient(EndpointConfiguration endpoints) + { + endpoint = endpoints.SpectatorEndpointUrl; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + connector = api.GetHubConnector(nameof(SpectatorClient), endpoint); + + if (connector != null) + { + connector.ConfigureConnection = connection => + { + // until strong typed client support is added, each method must be manually bound + // (see https://github.com/dotnet/aspnetcore/issues/15198) + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + }; + + IsConnected.BindTo(connector.IsConnected); + } + } + + protected override Task BeginPlayingInternal(SpectatorState state) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); + } + + protected override Task SendFramesInternal(FrameDataBundle data) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + } + + protected override Task EndPlayingInternal(SpectatorState state) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), state); + } + + protected override Task WatchUserInternal(int userId) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + } + + protected override Task StopWatchingUserInternal(int userId) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); + } + } +} diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 43115d577c..5ea31a49fb 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; -using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -23,21 +22,18 @@ using osu.Game.Screens.Play; namespace osu.Game.Online.Spectator { - public class SpectatorClient : Component, ISpectatorClient + public abstract class SpectatorClient : Component, ISpectatorClient { /// /// The maximum milliseconds between frame bundle sends. /// public const double TIME_BETWEEN_SENDS = 200; - private readonly string endpoint; - - [CanBeNull] - private IHubClientConnector connector; - - private readonly IBindable isConnected = new BindableBool(); - - private HubConnection connection => connector?.CurrentConnection; + /// + /// Whether the is currently connected. + /// This is NOT thread safe and usage should be scheduled. + /// + public abstract IBindable IsConnected { get; } private readonly List watchingUsers = new List(); @@ -63,7 +59,7 @@ namespace osu.Game.Online.Spectator private readonly SpectatorState currentState = new SpectatorState(); - private bool isPlaying; + protected bool IsPlaying { get; private set; } /// /// Called whenever new frames arrive from the server. @@ -80,59 +76,39 @@ namespace osu.Game.Online.Spectator /// public event Action OnUserFinishedPlaying; - public SpectatorClient(EndpointConfiguration endpoints) - { - endpoint = endpoints.SpectatorEndpointUrl; - } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load() { - connector = api.GetHubConnector(nameof(SpectatorClient), endpoint); - - if (connector != null) + IsConnected.BindValueChanged(connected => { - connector.ConfigureConnection = connection => + if (connected.NewValue) { - // until strong typed client support is added, each method must be manually bound - // (see https://github.com/dotnet/aspnetcore/issues/15198) - connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); - connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); - }; + // get all the users that were previously being watched + int[] users; - isConnected.BindTo(connector.IsConnected); - isConnected.BindValueChanged(connected => + lock (userLock) + { + users = watchingUsers.ToArray(); + watchingUsers.Clear(); + } + + // resubscribe to watched users. + foreach (var userId in users) + WatchUser(userId); + + // re-send state in case it wasn't received + if (IsPlaying) + BeginPlayingInternal(currentState); + } + else { - if (connected.NewValue) + lock (userLock) { - // get all the users that were previously being watched - int[] users; - - lock (userLock) - { - users = watchingUsers.ToArray(); - watchingUsers.Clear(); - } - - // resubscribe to watched users. - foreach (var userId in users) - WatchUser(userId); - - // re-send state in case it wasn't received - if (isPlaying) - beginPlaying(); + playingUsers.Clear(); + playingUserStates.Clear(); } - else - { - lock (userLock) - { - playingUsers.Clear(); - playingUserStates.Clear(); - } - } - }, true); - } + } + }, true); } Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) @@ -176,10 +152,10 @@ namespace osu.Game.Online.Spectator public void BeginPlaying(GameplayBeatmap beatmap, Score score) { - if (isPlaying) + if (IsPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); - isPlaying = true; + IsPlaying = true; // transfer state at point of beginning play currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; @@ -189,36 +165,20 @@ namespace osu.Game.Online.Spectator currentBeatmap = beatmap.PlayableBeatmap; currentScore = score; - beginPlaying(); + BeginPlayingInternal(currentState); } - private void beginPlaying() - { - Debug.Assert(isPlaying); - - if (!isConnected.Value) return; - - connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); - } - - public void SendFrames(FrameDataBundle data) - { - if (!isConnected.Value) return; - - lastSend = connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); - } + public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data); public void EndPlaying() { - isPlaying = false; + IsPlaying = false; currentBeatmap = null; - if (!isConnected.Value) return; - - connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); + EndPlayingInternal(currentState); } - public virtual void WatchUser(int userId) + public void WatchUser(int userId) { lock (userLock) { @@ -226,27 +186,31 @@ namespace osu.Game.Online.Spectator return; watchingUsers.Add(userId); - - if (!isConnected.Value) - return; } - connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + WatchUserInternal(userId); } - public virtual void StopWatchingUser(int userId) + public void StopWatchingUser(int userId) { lock (userLock) { watchingUsers.Remove(userId); - - if (!isConnected.Value) - return; } - connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); + StopWatchingUserInternal(userId); } + protected abstract Task BeginPlayingInternal(SpectatorState state); + + protected abstract Task SendFramesInternal(FrameDataBundle data); + + protected abstract Task EndPlayingInternal(SpectatorState state); + + protected abstract Task WatchUserInternal(int userId); + + protected abstract Task StopWatchingUserInternal(int userId); + private readonly Queue pendingFrames = new Queue(); private double lastSendTime; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3707e3b7be..41984839ab 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -240,7 +240,7 @@ namespace osu.Game dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash)); - dependencies.CacheAs(spectatorClient = new SpectatorClient(endpoints)); + dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new MultiplayerClient(endpoints)); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); From 750a5c3ea96530fae27195fd5afb0fe89ff4dd0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 17:20:30 +0900 Subject: [PATCH 227/304] Fix test compilation error --- .../Visual/Spectator/TestSpectatorClient.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 985e293981..6bd9f1a920 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Utils; -using osu.Game.Online; +using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Scoring; @@ -14,16 +18,16 @@ namespace osu.Game.Tests.Visual.Spectator { public class TestSpectatorClient : SpectatorClient { + public override IBindable IsConnected { get; } = new Bindable(true); + public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; private readonly ConcurrentDictionary watchingUsers = new ConcurrentDictionary(); private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userSentStateDictionary = new Dictionary(); - public TestSpectatorClient() - : base(new DevelopmentEndpointConfiguration()) - { - } + [Resolved] + private IAPIProvider api { get; set; } = null!; public void StartPlay(int userId, int beatmapId) { @@ -61,19 +65,25 @@ namespace osu.Game.Tests.Visual.Spectator sendState(userId, userBeatmapDictionary[userId]); } - public override void WatchUser(int userId) - { - base.WatchUser(userId); + protected override Task BeginPlayingInternal(SpectatorState state) => ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); + protected override Task SendFramesInternal(FrameDataBundle data) => ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, data); + + protected override Task EndPlayingInternal(SpectatorState state) => ((ISpectatorClient)this).UserFinishedPlaying(api.LocalUser.Value.Id, state); + + protected override Task WatchUserInternal(int userId) + { // When newly watching a user, the server sends the playing state immediately. if (watchingUsers.TryAdd(userId, 0) && PlayingUsers.Contains(userId)) sendState(userId, userBeatmapDictionary[userId]); + + return Task.CompletedTask; } - public override void StopWatchingUser(int userId) + protected override Task StopWatchingUserInternal(int userId) { - base.StopWatchingUser(userId); watchingUsers.TryRemove(userId, out _); + return Task.CompletedTask; } private void sendState(int userId, int beatmapId) From 9d07749959aa346be7106918e9411999e1cd21d9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 17:41:46 +0900 Subject: [PATCH 228/304] Improve implementation of TestSpectatorClient There was a lot of weirdness here, such as storing the playing users, clearing the playing users from test scenes (!!), and storing the users being wathed. This was all a thing because the previous implementation overrode the base method implementations, which is no longer a thing. --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../TestSceneMultiSpectatorLeaderboard.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 6 +- .../TestSceneCurrentlyPlayingDisplay.cs | 8 ++- .../Visual/Spectator/TestSpectatorClient.cs | 62 +++++++++++-------- 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 56a4ab8cba..e9894ff469 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorClient.EndPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); + private void finish() => AddStep("end play", () => testSpectatorClient.EndPlay(streamingUser.Id)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index afd4401a63..5ad35be0ec 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (var (userId, clock) in clocks) { - spectatorClient.EndPlay(userId, 0); + spectatorClient.EndPlay(userId); clock.CurrentTime = 0; } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 23095a1ea8..b91391c409 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("finish previous gameplay", () => { foreach (var id in playingUserIds) - spectatorClient.EndPlay(id, importedBeatmapId); + spectatorClient.EndPlay(id); playingUserIds.Clear(); }); } @@ -258,11 +258,11 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void finish(int userId, int? beatmapId = null) + private void finish(int userId) { AddStep("end play", () => { - spectatorClient.EndPlay(userId, beatmapId ?? importedBeatmapId); + spectatorClient.EndPlay(userId); playingUserIds.Remove(userId); nextFrame.Remove(userId); }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index 9bc0b32eee..30785fd163 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online { public class TestSceneCurrentlyPlayingDisplay : OsuTestScene { + private readonly User streamingUser = new User { Id = 2, Username = "Test user" }; + [Cached(typeof(SpectatorClient))] private TestSpectatorClient testSpectatorClient = new TestSpectatorClient(); @@ -55,15 +57,15 @@ namespace osu.Game.Tests.Visual.Online }; }); - AddStep("Reset players", () => testSpectatorClient.PlayingUsers.Clear()); + AddStep("Reset players", () => testSpectatorClient.EndPlay(streamingUser.Id)); } [Test] public void TestBasicDisplay() { - AddStep("Add playing user", () => testSpectatorClient.PlayingUsers.Add(2)); + AddStep("Add playing user", () => testSpectatorClient.StartPlay(streamingUser.Id, 0)); AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType()?.FirstOrDefault()?.User.Id == 2); - AddStep("Remove playing user", () => testSpectatorClient.PlayingUsers.Remove(2)); + AddStep("Remove playing user", () => testSpectatorClient.EndPlay(streamingUser.Id)); AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType().Any()); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 6bd9f1a920..3a5ffa8770 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -3,8 +3,9 @@ #nullable enable -using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -20,33 +21,44 @@ namespace osu.Game.Tests.Visual.Spectator { public override IBindable IsConnected { get; } = new Bindable(true); - public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; - private readonly ConcurrentDictionary watchingUsers = new ConcurrentDictionary(); - private readonly Dictionary userBeatmapDictionary = new Dictionary(); - private readonly Dictionary userSentStateDictionary = new Dictionary(); [Resolved] private IAPIProvider api { get; set; } = null!; + /// + /// Starts play for an arbitrary user. + /// + /// The user to start play for. + /// The playing beatmap id. public void StartPlay(int userId, int beatmapId) { userBeatmapDictionary[userId] = beatmapId; - sendState(userId, beatmapId); + sendPlayingState(userId); } - public void EndPlay(int userId, int beatmapId) + /// + /// Ends play for an arbitrary user. + /// + /// The user to end play for. + public void EndPlay(int userId) { + if (!PlayingUsers.Contains(userId)) + return; + ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState { - BeatmapID = beatmapId, + BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, }); - - userBeatmapDictionary.Remove(userId); - userSentStateDictionary.Remove(userId); } + /// + /// Sends frames for an arbitrary user. + /// + /// The user to send frames for. + /// The frame index. + /// The number of frames to send. public void SendFrames(int userId, int index, int count) { var frames = new List(); @@ -60,12 +72,16 @@ namespace osu.Game.Tests.Visual.Spectator var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); ((ISpectatorClient)this).UserSentFrames(userId, bundle); - - if (!userSentStateDictionary[userId]) - sendState(userId, userBeatmapDictionary[userId]); } - protected override Task BeginPlayingInternal(SpectatorState state) => ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); + protected override Task BeginPlayingInternal(SpectatorState state) + { + // Track the local user's playing beatmap ID. + Debug.Assert(state.BeatmapID != null); + userBeatmapDictionary[api.LocalUser.Value.Id] = state.BeatmapID.Value; + + return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); + } protected override Task SendFramesInternal(FrameDataBundle data) => ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, data); @@ -74,27 +90,21 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task WatchUserInternal(int userId) { // When newly watching a user, the server sends the playing state immediately. - if (watchingUsers.TryAdd(userId, 0) && PlayingUsers.Contains(userId)) - sendState(userId, userBeatmapDictionary[userId]); + if (PlayingUsers.Contains(userId)) + sendPlayingState(userId); return Task.CompletedTask; } - protected override Task StopWatchingUserInternal(int userId) - { - watchingUsers.TryRemove(userId, out _); - return Task.CompletedTask; - } + protected override Task StopWatchingUserInternal(int userId) => Task.CompletedTask; - private void sendState(int userId, int beatmapId) + private void sendPlayingState(int userId) { ((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState { - BeatmapID = beatmapId, + BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, }); - - userSentStateDictionary[userId] = true; } } } From 6eff8d513e6c34b3efd8c88bdf17ea698a977943 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 17:51:09 +0900 Subject: [PATCH 229/304] Annotate nullables --- osu.Game/Online/Spectator/SpectatorClient.cs | 24 +++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 5ea31a49fb..cb98b01bed 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -1,12 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -45,36 +46,37 @@ namespace osu.Game.Online.Spectator private readonly Dictionary playingUserStates = new Dictionary(); - [CanBeNull] - private IBeatmap currentBeatmap; + private IBeatmap? currentBeatmap; - [CanBeNull] - private Score currentScore; + private Score? currentScore; [Resolved] - private IBindable currentRuleset { get; set; } + private IBindable currentRuleset { get; set; } = null!; [Resolved] - private IBindable> currentMods { get; set; } + private IBindable> currentMods { get; set; } = null!; private readonly SpectatorState currentState = new SpectatorState(); + /// + /// Whether the local user is playing. + /// protected bool IsPlaying { get; private set; } /// /// Called whenever new frames arrive from the server. /// - public event Action OnNewFrames; + public event Action? OnNewFrames; /// /// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session. /// - public event Action OnUserBeganPlaying; + public event Action? OnUserBeganPlaying; /// /// Called whenever a user finishes a play session. /// - public event Action OnUserFinishedPlaying; + public event Action? OnUserFinishedPlaying; [BackgroundDependencyLoader] private void load() @@ -215,7 +217,7 @@ namespace osu.Game.Online.Spectator private double lastSendTime; - private Task lastSend; + private Task? lastSend; private const int max_pending_frames = 30; From 27e81d6504aff75e4e0673496c878c8fb0698ac8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 18:21:16 +0900 Subject: [PATCH 230/304] Implement proper rotation algorithm for skin editor --- .../Edit/OsuSelectionHandler.cs | 30 ++-------------- .../Compose/Components/SelectionHandler.cs | 24 +++++++++++++ .../Skinning/Editor/SkinSelectionHandler.cs | 34 ++++++++++++++++--- 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 8cb86bc108..57d0cd859d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; @@ -12,7 +11,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; -using Vector2 = osuTK.Vector2; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { @@ -173,12 +172,12 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var h in hitObjects) { - h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); + h.Position = RotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); if (h is IHasPath path) { foreach (var point in path.Path.ControlPoints) - point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); + point.Position.Value = RotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); } } @@ -324,28 +323,5 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType() .Where(h => !(h is Spinner)) .ToArray(); - - /// - /// Rotate a point around an arbitrary origin. - /// - /// The point. - /// The centre origin to rotate around. - /// The angle to rotate (in degrees). - private static Vector2 rotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) - { - angle = -angle; - - point.X -= origin.X; - point.Y -= origin.Y; - - Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); - - ret.X += origin.X; - ret.Y += origin.Y; - - return ret; - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 26328b4dc7..8939be925a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -353,6 +354,29 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Helper Methods + /// + /// Rotate a point around an arbitrary origin. + /// + /// The point. + /// The centre origin to rotate around. + /// The angle to rotate (in degrees). + protected static Vector2 RotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) + { + angle = -angle; + + point.X -= origin.X; + point.Y -= origin.Y; + + Vector2 ret; + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); + + ret.X += origin.X; + ret.Y += origin.Y; + + return ret; + } + /// /// Given a flip direction, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2f9611ba65..cc4c120e23 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -18,16 +18,42 @@ namespace osu.Game.Skinning.Editor { public class SkinSelectionHandler : SelectionHandler { + private Vector2? referenceOrigin; + [Resolved] private SkinEditor skinEditor { get; set; } + protected override void OnOperationEnded() + { + base.OnOperationEnded(); + referenceOrigin = null; + } + public override bool HandleRotation(float angle) { - // TODO: this doesn't correctly account for origin/anchor specs being different in a multi-selection. - foreach (var c in SelectedBlueprints) - ((Drawable)c.Item).Rotation += angle; + if (SelectedBlueprints.Count == 1) + { + // for single items, rotate around the origin rather than the selection centre. + ((Drawable)SelectedBlueprints.First().Item).Rotation += angle; + } + else + { + var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => + b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); - return base.HandleRotation(angle); + referenceOrigin ??= selectionQuad.Centre; + + foreach (var b in SelectedBlueprints) + { + var drawableItem = (Drawable)b.Item; + + drawableItem.Position = drawableItem.Parent.ToLocalSpace(RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, referenceOrigin.Value, angle)) - drawableItem.AnchorPosition; + drawableItem.Rotation += angle; + } + } + + // this isn't always the case but let's be lenient for now. + return true; } public override bool HandleScale(Vector2 scale, Anchor anchor) From 95c78b918510e8c4ebbc68a95496e0956a676278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 18:24:25 +0900 Subject: [PATCH 231/304] Split out common selection quad logic --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index cc4c120e23..ec45830f94 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; @@ -38,8 +39,7 @@ namespace osu.Game.Skinning.Editor } else { - var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => - b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + var selectionQuad = getSelectionQuad(); referenceOrigin ??= selectionQuad.Centre; @@ -63,8 +63,7 @@ namespace osu.Game.Skinning.Editor adjustScaleFromAnchor(ref scale, anchor); - var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => - b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + var selectionQuad = getSelectionQuad(); // the selection quad is always upright, so use a rect to make mutating the values easier. var adjustedRect = selectionQuad.AABBFloat; @@ -220,6 +219,13 @@ namespace osu.Game.Skinning.Editor } } + /// + /// A screen-space quad surrounding all selected drawables, accounting for their full displayed size. + /// + /// + private Quad getSelectionQuad() => + GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + private void applyAnchor(Anchor anchor) { foreach (var item in SelectedItems) From 6f75c59760e3bca0110e772c4e48a65addd496a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 18:31:51 +0900 Subject: [PATCH 232/304] Fix flip logic not using the full selection quad --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index ec45830f94..88372e23a3 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -125,7 +125,7 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction) { - var selectionQuad = GetSurroundingQuad(SelectedBlueprints.Select(b => b.ScreenSpaceSelectionPoint)); + var selectionQuad = getSelectionQuad(); foreach (var b in SelectedBlueprints) { From 20f1ef43180b20c8ee310c62a262f0f06bf3b5c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 18:35:13 +0900 Subject: [PATCH 233/304] Extract common implementation of updating drawable position from screen space pos --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 88372e23a3..bdb1d1c054 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -47,7 +47,8 @@ namespace osu.Game.Skinning.Editor { var drawableItem = (Drawable)b.Item; - drawableItem.Position = drawableItem.Parent.ToLocalSpace(RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, referenceOrigin.Value, angle)) - drawableItem.AnchorPosition; + var rotatedPosition = RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, referenceOrigin.Value, angle); + updateDrawablePosition(drawableItem, rotatedPosition); drawableItem.Rotation += angle; } } @@ -116,7 +117,7 @@ namespace osu.Game.Skinning.Editor adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y ); - drawableItem.Position = drawableItem.Parent.ToLocalSpace(newPositionInAdjusted) - drawableItem.AnchorPosition; + updateDrawablePosition(drawableItem, newPositionInAdjusted); drawableItem.Scale *= scaledDelta; } @@ -131,8 +132,9 @@ namespace osu.Game.Skinning.Editor { var drawableItem = (Drawable)b.Item; - drawableItem.Position = - drawableItem.Parent.ToLocalSpace(GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint)) - drawableItem.AnchorPosition; + var flippedPosition = GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint); + + updateDrawablePosition(drawableItem, flippedPosition); drawableItem.Scale *= new Vector2( direction == Direction.Horizontal ? -1 : 1, @@ -207,6 +209,12 @@ namespace osu.Game.Skinning.Editor } } + private static void updateDrawablePosition(Drawable drawable, Vector2 screenSpacePosition) + { + drawable.Position = + drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition; + } + private void applyOrigin(Anchor anchor) { foreach (var item in SelectedItems) From 10597f7e6a508de8db6aade5faf6664c22f26d46 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 18:37:27 +0900 Subject: [PATCH 234/304] Remove locking from SpectatorClient --- osu.Game/Online/Spectator/SpectatorClient.cs | 66 +++++++------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index cb98b01bed..f930328846 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -38,8 +38,6 @@ namespace osu.Game.Online.Spectator private readonly List watchingUsers = new List(); - private readonly object userLock = new object(); - public IBindableList PlayingUsers => playingUsers; private readonly BindableList playingUsers = new BindableList(); @@ -81,18 +79,13 @@ namespace osu.Game.Online.Spectator [BackgroundDependencyLoader] private void load() { - IsConnected.BindValueChanged(connected => + IsConnected.BindValueChanged(connected => Schedule(() => { if (connected.NewValue) { // get all the users that were previously being watched - int[] users; - - lock (userLock) - { - users = watchingUsers.ToArray(); - watchingUsers.Clear(); - } + int[] users = watchingUsers.ToArray(); + watchingUsers.Clear(); // resubscribe to watched users. foreach (var userId in users) @@ -104,18 +97,15 @@ namespace osu.Game.Online.Spectator } else { - lock (userLock) - { - playingUsers.Clear(); - playingUserStates.Clear(); - } + playingUsers.Clear(); + playingUserStates.Clear(); } - }, true); + }), true); } Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { - lock (userLock) + Schedule(() => { if (!playingUsers.Contains(userId)) playingUsers.Add(userId); @@ -125,29 +115,29 @@ namespace osu.Game.Online.Spectator // We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations. if (watchingUsers.Contains(userId)) playingUserStates[userId] = state; - } - OnUserBeganPlaying?.Invoke(userId, state); + OnUserBeganPlaying?.Invoke(userId, state); + }); return Task.CompletedTask; } Task ISpectatorClient.UserFinishedPlaying(int userId, SpectatorState state) { - lock (userLock) + Schedule(() => { playingUsers.Remove(userId); playingUserStates.Remove(userId); - } - OnUserFinishedPlaying?.Invoke(userId, state); + OnUserFinishedPlaying?.Invoke(userId, state); + }); return Task.CompletedTask; } Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data) { - OnNewFrames?.Invoke(userId, data); + Schedule(() => OnNewFrames?.Invoke(userId, data)); return Task.CompletedTask; } @@ -182,23 +172,17 @@ namespace osu.Game.Online.Spectator public void WatchUser(int userId) { - lock (userLock) - { - if (watchingUsers.Contains(userId)) - return; + if (watchingUsers.Contains(userId)) + return; - watchingUsers.Add(userId); - } + watchingUsers.Add(userId); WatchUserInternal(userId); } public void StopWatchingUser(int userId) { - lock (userLock) - { - watchingUsers.Remove(userId); - } + watchingUsers.Remove(userId); StopWatchingUserInternal(userId); } @@ -262,8 +246,7 @@ namespace osu.Game.Online.Spectator /// true if successful (the user is playing), false otherwise. public bool TryGetPlayingUserState(int userId, out SpectatorState state) { - lock (userLock) - return playingUserStates.TryGetValue(userId, out state); + return playingUserStates.TryGetValue(userId, out state); } /// @@ -274,16 +257,13 @@ namespace osu.Game.Online.Spectator public void BindUserBeganPlaying(Action callback, bool runOnceImmediately = false) { // The lock is taken before the event is subscribed to to prevent doubling of events. - lock (userLock) - { - OnUserBeganPlaying += callback; + OnUserBeganPlaying += callback; - if (!runOnceImmediately) - return; + if (!runOnceImmediately) + return; - foreach (var (userId, state) in playingUserStates) - callback(userId, state); - } + foreach (var (userId, state) in playingUserStates) + callback(userId, state); } } } From f74dbb9e1f2abfafca3bfe4499c5488dc6198c49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 18:52:20 +0900 Subject: [PATCH 235/304] Remove locking from SpectatorScreen --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 181 ++++++++----------- 1 file changed, 78 insertions(+), 103 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 1cf7bc30ee..e6c9a0acd4 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -42,9 +42,6 @@ namespace osu.Game.Screens.Spectate [Resolved] private UserLookupCache userLookupCache { get; set; } - // A lock is used to synchronise access to spectator/gameplay states, since this class is a screen which may become non-current and stop receiving updates at any point. - private readonly object stateLock = new object(); - private readonly Dictionary userMap = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); @@ -63,8 +60,11 @@ namespace osu.Game.Screens.Spectate { base.LoadComplete(); - populateAllUsers().ContinueWith(_ => Schedule(() => + getAllUsers().ContinueWith(users => Schedule(() => { + foreach (var u in users.Result) + userMap[u.Id] = u; + spectatorClient.BindUserBeganPlaying(userBeganPlaying, true); spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; spectatorClient.OnNewFrames += userSentFrames; @@ -72,27 +72,23 @@ namespace osu.Game.Screens.Spectate managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - lock (stateLock) - { - foreach (var (id, _) in userMap) - spectatorClient.WatchUser(id); - } + foreach (var (id, _) in userMap) + spectatorClient.WatchUser(id); })); } - private Task populateAllUsers() + private Task getAllUsers() { - var userLookupTasks = new List(); + var userLookupTasks = new List>(); foreach (var u in userIds) { userLookupTasks.Add(userLookupCache.GetUserAsync(u).ContinueWith(task => { if (!task.IsCompletedSuccessfully) - return; + return null; - lock (stateLock) - userMap[u] = task.Result; + return task.Result; })); } @@ -104,16 +100,13 @@ namespace osu.Game.Screens.Spectate if (!e.NewValue.TryGetTarget(out var beatmapSet)) return; - lock (stateLock) + foreach (var (userId, _) in userMap) { - foreach (var (userId, _) in userMap) - { - if (!spectatorClient.TryGetPlayingUserState(userId, out var userState)) - continue; + if (!spectatorClient.TryGetPlayingUserState(userId, out var userState)) + continue; - if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) - updateGameplayState(userId); - } + if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) + updateGameplayState(userId); } } @@ -122,101 +115,89 @@ namespace osu.Game.Screens.Spectate if (state.RulesetID == null || state.BeatmapID == null) return; - lock (stateLock) - { - if (!userMap.ContainsKey(userId)) - return; + if (!userMap.ContainsKey(userId)) + return; - // The user may have stopped playing. - if (!spectatorClient.TryGetPlayingUserState(userId, out _)) - return; + // The user may have stopped playing. + if (!spectatorClient.TryGetPlayingUserState(userId, out _)) + return; - Schedule(() => OnUserStateChanged(userId, state)); + Schedule(() => OnUserStateChanged(userId, state)); - updateGameplayState(userId); - } + updateGameplayState(userId); } private void updateGameplayState(int userId) { - lock (stateLock) + Debug.Assert(userMap.ContainsKey(userId)); + + // The user may have stopped playing. + if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState)) + return; + + var user = userMap[userId]; + + var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance(); + if (resolvedRuleset == null) + return; + + var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID); + if (resolvedBeatmap == null) + return; + + var score = new Score { - Debug.Assert(userMap.ContainsKey(userId)); - - // The user may have stopped playing. - if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState)) - return; - - var user = userMap[userId]; - - var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance(); - if (resolvedRuleset == null) - return; - - var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID); - if (resolvedBeatmap == null) - return; - - var score = new Score + ScoreInfo = new ScoreInfo { - ScoreInfo = new ScoreInfo - { - Beatmap = resolvedBeatmap, - User = user, - Mods = spectatorState.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), - Ruleset = resolvedRuleset.RulesetInfo, - }, - Replay = new Replay { HasReceivedAllFrames = false }, - }; + Beatmap = resolvedBeatmap, + User = user, + Mods = spectatorState.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), + Ruleset = resolvedRuleset.RulesetInfo, + }, + Replay = new Replay { HasReceivedAllFrames = false }, + }; - var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); + var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); - gameplayStates[userId] = gameplayState; - Schedule(() => StartGameplay(userId, gameplayState)); - } + gameplayStates[userId] = gameplayState; + Schedule(() => StartGameplay(userId, gameplayState)); } private void userSentFrames(int userId, FrameDataBundle bundle) { - lock (stateLock) + if (!userMap.ContainsKey(userId)) + return; + + if (!gameplayStates.TryGetValue(userId, out var gameplayState)) + return; + + // The ruleset instance should be guaranteed to be in sync with the score via ScoreLock. + Debug.Assert(gameplayState.Ruleset != null && gameplayState.Ruleset.RulesetInfo.Equals(gameplayState.Score.ScoreInfo.Ruleset)); + + foreach (var frame in bundle.Frames) { - if (!userMap.ContainsKey(userId)) - return; + IConvertibleReplayFrame convertibleFrame = gameplayState.Ruleset.CreateConvertibleReplayFrame(); + convertibleFrame.FromLegacy(frame, gameplayState.Beatmap.Beatmap); - if (!gameplayStates.TryGetValue(userId, out var gameplayState)) - return; + var convertedFrame = (ReplayFrame)convertibleFrame; + convertedFrame.Time = frame.Time; - // The ruleset instance should be guaranteed to be in sync with the score via ScoreLock. - Debug.Assert(gameplayState.Ruleset != null && gameplayState.Ruleset.RulesetInfo.Equals(gameplayState.Score.ScoreInfo.Ruleset)); - - foreach (var frame in bundle.Frames) - { - IConvertibleReplayFrame convertibleFrame = gameplayState.Ruleset.CreateConvertibleReplayFrame(); - convertibleFrame.FromLegacy(frame, gameplayState.Beatmap.Beatmap); - - var convertedFrame = (ReplayFrame)convertibleFrame; - convertedFrame.Time = frame.Time; - - gameplayState.Score.Replay.Frames.Add(convertedFrame); - } + gameplayState.Score.Replay.Frames.Add(convertedFrame); } } private void userFinishedPlaying(int userId, SpectatorState state) { - lock (stateLock) - { - if (!userMap.ContainsKey(userId)) - return; + if (!userMap.ContainsKey(userId)) + return; - if (!gameplayStates.TryGetValue(userId, out var gameplayState)) - return; + if (!gameplayStates.TryGetValue(userId, out var gameplayState)) + return; - gameplayState.Score.Replay.HasReceivedAllFrames = true; + gameplayState.Score.Replay.HasReceivedAllFrames = true; - gameplayStates.Remove(userId); - Schedule(() => EndGameplay(userId)); - } + gameplayStates.Remove(userId); + Schedule(() => EndGameplay(userId)); } /// @@ -245,15 +226,12 @@ namespace osu.Game.Screens.Spectate /// The user to stop spectating. protected void RemoveUser(int userId) { - lock (stateLock) - { - userFinishedPlaying(userId, null); + userFinishedPlaying(userId, null); - userIds.Remove(userId); - userMap.Remove(userId); + userIds.Remove(userId); + userMap.Remove(userId); - spectatorClient.StopWatchingUser(userId); - } + spectatorClient.StopWatchingUser(userId); } protected override void Dispose(bool isDisposing) @@ -266,11 +244,8 @@ namespace osu.Game.Screens.Spectate spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying; spectatorClient.OnNewFrames -= userSentFrames; - lock (stateLock) - { - foreach (var (userId, _) in userMap) - spectatorClient.StopWatchingUser(userId); - } + foreach (var (userId, _) in userMap) + spectatorClient.StopWatchingUser(userId); } managerUpdated?.UnbindAll(); From 89b4f695884ca229193dc8e7bd2b5d009847a55b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 19:19:39 +0900 Subject: [PATCH 236/304] Expose playing user states as bindable dictionary --- osu.Game/Online/Spectator/SpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index f930328846..810299e90d 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -39,10 +39,10 @@ namespace osu.Game.Online.Spectator private readonly List watchingUsers = new List(); public IBindableList PlayingUsers => playingUsers; - private readonly BindableList playingUsers = new BindableList(); - private readonly Dictionary playingUserStates = new Dictionary(); + public IBindableDictionary PlayingUserStates => playingUserStates; + private readonly BindableDictionary playingUserStates = new BindableDictionary(); private IBeatmap? currentBeatmap; From b515fe3cb1f9ee8732bd427dfc842cbefca3a9b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 19:19:58 +0900 Subject: [PATCH 237/304] Fix playing user state not removed on stop watching --- osu.Game/Online/Spectator/SpectatorClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 810299e90d..649af03bc4 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -183,6 +183,7 @@ namespace osu.Game.Online.Spectator public void StopWatchingUser(int userId) { watchingUsers.Remove(userId); + playingUserStates.Remove(userId); StopWatchingUserInternal(userId); } From 7ee81669f7a304982bb88cccf5cf354a9cc72131 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 19:27:34 +0900 Subject: [PATCH 238/304] Remove bind helpers from SpectatorClient --- osu.Game/Online/Spectator/SpectatorClient.cs | 28 -------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 68 +++++++++++--------- 2 files changed, 39 insertions(+), 57 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 649af03bc4..04904f66ac 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -238,33 +238,5 @@ namespace osu.Game.Online.Spectator lastSendTime = Time.Current; } - - /// - /// Attempts to retrieve the for a currently-playing user. - /// - /// The user. - /// The current for the user, if they're playing. null if the user is not playing. - /// true if successful (the user is playing), false otherwise. - public bool TryGetPlayingUserState(int userId, out SpectatorState state) - { - return playingUserStates.TryGetValue(userId, out state); - } - - /// - /// Bind an action to with the option of running the bound action once immediately. - /// - /// The action to perform when a user begins playing. - /// Whether the action provided in should be run once immediately for all users currently playing. - public void BindUserBeganPlaying(Action callback, bool runOnceImmediately = false) - { - // The lock is taken before the event is subscribed to to prevent doubling of events. - OnUserBeganPlaying += callback; - - if (!runOnceImmediately) - return; - - foreach (var (userId, state) in playingUserStates) - callback(userId, state); - } } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index e6c9a0acd4..0adc5b863f 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.Spectator; @@ -42,6 +43,8 @@ namespace osu.Game.Screens.Spectate [Resolved] private UserLookupCache userLookupCache { get; set; } + private readonly IBindableDictionary playingUserStates = new BindableDictionary(); + private readonly Dictionary userMap = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); @@ -65,8 +68,9 @@ namespace osu.Game.Screens.Spectate foreach (var u in users.Result) userMap[u.Id] = u; - spectatorClient.BindUserBeganPlaying(userBeganPlaying, true); - spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; + playingUserStates.BindTo(spectatorClient.PlayingUserStates); + playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); + spectatorClient.OnNewFrames += userSentFrames; managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); @@ -102,7 +106,7 @@ namespace osu.Game.Screens.Spectate foreach (var (userId, _) in userMap) { - if (!spectatorClient.TryGetPlayingUserState(userId, out var userState)) + if (!playingUserStates.TryGetValue(userId, out var userState)) continue; if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) @@ -110,7 +114,23 @@ namespace osu.Game.Screens.Spectate } } - private void userBeganPlaying(int userId, SpectatorState state) + private void onPlayingUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) + { + switch (e.Action) + { + case NotifyDictionaryChangedAction.Add: + foreach (var (userId, state) in e.NewItems.AsNonNull()) + onUserStateAdded(userId, state); + break; + + case NotifyDictionaryChangedAction.Remove: + foreach (var (userId, _) in e.OldItems.AsNonNull()) + onUserStateRemoved(userId); + break; + } + } + + private void onUserStateAdded(int userId, SpectatorState state) { if (state.RulesetID == null || state.BeatmapID == null) return; @@ -118,24 +138,30 @@ namespace osu.Game.Screens.Spectate if (!userMap.ContainsKey(userId)) return; - // The user may have stopped playing. - if (!spectatorClient.TryGetPlayingUserState(userId, out _)) + Schedule(() => OnUserStateChanged(userId, state)); + updateGameplayState(userId); + } + + private void onUserStateRemoved(int userId) + { + if (!userMap.ContainsKey(userId)) return; - Schedule(() => OnUserStateChanged(userId, state)); + if (!gameplayStates.TryGetValue(userId, out var gameplayState)) + return; - updateGameplayState(userId); + gameplayState.Score.Replay.HasReceivedAllFrames = true; + + gameplayStates.Remove(userId); + Schedule(() => EndGameplay(userId)); } private void updateGameplayState(int userId) { Debug.Assert(userMap.ContainsKey(userId)); - // The user may have stopped playing. - if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState)) - return; - var user = userMap[userId]; + var spectatorState = playingUserStates[userId]; var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance(); if (resolvedRuleset == null) @@ -186,20 +212,6 @@ namespace osu.Game.Screens.Spectate } } - private void userFinishedPlaying(int userId, SpectatorState state) - { - if (!userMap.ContainsKey(userId)) - return; - - if (!gameplayStates.TryGetValue(userId, out var gameplayState)) - return; - - gameplayState.Score.Replay.HasReceivedAllFrames = true; - - gameplayStates.Remove(userId); - Schedule(() => EndGameplay(userId)); - } - /// /// Invoked when a spectated user's state has changed. /// @@ -226,7 +238,7 @@ namespace osu.Game.Screens.Spectate /// The user to stop spectating. protected void RemoveUser(int userId) { - userFinishedPlaying(userId, null); + onUserStateRemoved(userId); userIds.Remove(userId); userMap.Remove(userId); @@ -240,8 +252,6 @@ namespace osu.Game.Screens.Spectate if (spectatorClient != null) { - spectatorClient.OnUserBeganPlaying -= userBeganPlaying; - spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying; spectatorClient.OnNewFrames -= userSentFrames; foreach (var (userId, _) in userMap) From df5970fab4585e6649a2cec85f7d38e5ee47b264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 19:34:53 +0900 Subject: [PATCH 239/304] Create base implementations of the two most common `TernaryStateMenuItem`s --- .../Components/PathControlPointVisualiser.cs | 12 +--- .../Edit/TaikoSelectionHandler.cs | 4 +- .../TestSceneStatefulMenuItem.cs | 56 +++++++++++++++++-- .../UserInterface/TernaryStateMenuItem.cs | 37 ++---------- .../TernaryStateRadioMenuItem.cs | 23 ++++++++ .../TernaryStateToggleMenuItem.cs | 42 ++++++++++++++ .../Components/EditorSelectionHandler.cs | 4 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- .../Skinning/Editor/SkinSelectionHandler.cs | 14 +---- 9 files changed, 129 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs create mode 100644 osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 44c3056910..c36768baba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type); - var item = new PathTypeMenuItem(type, () => + var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); @@ -258,15 +258,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return item; } - - private class PathTypeMenuItem : TernaryStateMenuItem - { - public PathTypeMenuItem(PathType? type, Action action) - : base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke()) - { - } - - private static TernaryState changeState(TernaryState state) => TernaryState.True; - } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 48ee0d4cf4..a24130d6ac 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -76,10 +76,10 @@ namespace osu.Game.Rulesets.Taiko.Edit protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { if (selection.All(s => s.Item is Hit)) - yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } }; + yield return new TernaryStateToggleMenuItem("Rim") { State = { BindTarget = selectionRimState } }; if (selection.All(s => s.Item is TaikoHitObject)) - yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; + yield return new TernaryStateToggleMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index 29aeb6a4b2..18ec631f37 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestSceneStatefulMenuItem : OsuManualInputManagerTestScene { [Test] - public void TestTernaryMenuItem() + public void TestTernaryRadioMenuItem() { OsuMenu menu = null; @@ -30,9 +30,57 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Items = new[] { - new TernaryStateMenuItem("First"), - new TernaryStateMenuItem("Second") { State = { BindTarget = state } }, - new TernaryStateMenuItem("Third") { State = { Value = TernaryState.True } }, + new TernaryStateRadioMenuItem("First"), + new TernaryStateRadioMenuItem("Second") { State = { BindTarget = state } }, + new TernaryStateRadioMenuItem("Third") { State = { Value = TernaryState.True } }, + } + }; + }); + + checkState(TernaryState.Indeterminate); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.True); + + AddStep("change state via bindable", () => state.Value = TernaryState.True); + + void click() => + AddStep("click", () => + { + InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + }); + + void checkState(TernaryState expected) + => AddAssert($"state is {expected}", () => state.Value == expected); + } + + [Test] + public void TestTernaryToggleMenuItem() + { + OsuMenu menu = null; + + Bindable state = new Bindable(TernaryState.Indeterminate); + + AddStep("create menu", () => + { + state.Value = TernaryState.Indeterminate; + + Child = menu = new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new TernaryStateToggleMenuItem("First"), + new TernaryStateToggleMenuItem("Second") { State = { BindTarget = state } }, + new TernaryStateToggleMenuItem("Third") { State = { Value = TernaryState.True } }, } }; }); diff --git a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs index acf4065f49..5c623150b7 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs @@ -9,28 +9,17 @@ namespace osu.Game.Graphics.UserInterface /// /// An with three possible states. /// - public class TernaryStateMenuItem : StatefulMenuItem + public abstract class TernaryStateMenuItem : StatefulMenuItem { /// /// Creates a new . /// /// The text to display. + /// A function to inform what the next state should be when this item is clicked. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) - : this(text, getNextState, type, action) - { - } - - /// - /// Creates a new . - /// - /// The text to display. - /// A function that mutates a state to another state after this is pressed. - /// The type of action which this performs. - /// A delegate to be invoked when this is pressed. - protected TernaryStateMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) - : base(text, changeStateFunc, type, action) + protected TernaryStateMenuItem(string text, Func nextStateFunction, MenuItemType type = MenuItemType.Standard, Action action = null) + : base(text, nextStateFunction, type, action) { } @@ -47,23 +36,5 @@ namespace osu.Game.Graphics.UserInterface return null; } - - private static TernaryState getNextState(TernaryState state) - { - switch (state) - { - case TernaryState.False: - return TernaryState.True; - - case TernaryState.Indeterminate: - return TernaryState.True; - - case TernaryState.True: - return TernaryState.False; - - default: - throw new ArgumentOutOfRangeException(nameof(state), state, null); - } - } } } diff --git a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs new file mode 100644 index 0000000000..aa83b0567b --- /dev/null +++ b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Graphics.UserInterface +{ + public class TernaryStateRadioMenuItem : TernaryStateMenuItem + { + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. + public TernaryStateRadioMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) + : base(text, getNextState, type, action) + { + } + + private static TernaryState getNextState(TernaryState state) => TernaryState.True; + } +} diff --git a/osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs new file mode 100644 index 0000000000..ce951984fd --- /dev/null +++ b/osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A ternary state menu item which toggles the state of this item false if clicked when true. + /// + public class TernaryStateToggleMenuItem : TernaryStateMenuItem + { + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. + public TernaryStateToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) + : base(text, getNextState, type, action) + { + } + + private static TernaryState getNextState(TernaryState state) + { + switch (state) + { + case TernaryState.False: + return TernaryState.True; + + case TernaryState.Indeterminate: + return TernaryState.True; + + case TernaryState.True: + return TernaryState.False; + + default: + throw new ArgumentOutOfRangeException(nameof(state), state, null); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 6ab4ca8267..2141c490df 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -168,13 +168,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) { - yield return new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; + yield return new TernaryStateToggleMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; } yield return new OsuMenuItem("Sound") { Items = SelectionSampleStates.Select(kvp => - new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index d7e901b71e..a3fca3d4e1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; - return new TernaryStateMenuItem(collection.Name.Value, MenuItemType.Standard, s => + return new TernaryStateToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => { foreach (var b in beatmapSet.Beatmaps) { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2eb4ea107d..2cfb9d0f96 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -100,7 +100,7 @@ namespace osu.Game.Skinning.Editor foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) + IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) { var displayableAnchors = new[] { @@ -117,7 +117,7 @@ namespace osu.Game.Skinning.Editor return displayableAnchors.Select(a => { - return new AnchorMenuItem(a, selection, _ => applyFunction(a)) + return new TernaryStateRadioMenuItem(a.ToString(), MenuItemType.Standard, _ => applyFunction(a)) { State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == a) } }; @@ -166,15 +166,5 @@ namespace osu.Game.Skinning.Editor scale.Y = scale.X; } } - - public class AnchorMenuItem : TernaryStateMenuItem - { - public AnchorMenuItem(Anchor anchor, IEnumerable> selection, Action action) - : base(anchor.ToString(), getNextState, MenuItemType.Standard, action) - { - } - - private static TernaryState getNextState(TernaryState state) => TernaryState.True; - } } } From ee4bca9ed12d5f65cc55d3b24f1164851c89c746 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 19:37:43 +0900 Subject: [PATCH 240/304] Handle collection changed event --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 0adc5b863f..9a20bb58b8 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -127,6 +127,14 @@ namespace osu.Game.Screens.Spectate foreach (var (userId, _) in e.OldItems.AsNonNull()) onUserStateRemoved(userId); break; + + case NotifyDictionaryChangedAction.Replace: + foreach (var (userId, _) in e.OldItems.AsNonNull()) + onUserStateRemoved(userId); + + foreach (var (userId, state) in e.NewItems.AsNonNull()) + onUserStateAdded(userId, state); + break; } } From 5a8b8782d34e76faaad394c42cdd597289acf633 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 19:44:43 +0900 Subject: [PATCH 241/304] Fix WatchUser being called asynchronously in BDL --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index ed83bbf693..c3bfe19b29 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -55,8 +55,6 @@ namespace osu.Game.Screens.Play.HUD foreach (var userId in playingUsers) { - spectatorClient.WatchUser(userId); - // probably won't be required in the final implementation. var resolvedUser = userLookupCache.GetUserAsync(userId).Result; @@ -80,6 +78,8 @@ namespace osu.Game.Screens.Play.HUD // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. foreach (int userId in playingUsers) { + spectatorClient.WatchUser(userId); + if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(userId)) usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); } From 06c99e8c7c259e8ffb712ff5320163b38e0f4aa3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 19:45:11 +0900 Subject: [PATCH 242/304] Fix race due to StopWatchingUser() being called asynchronously --- osu.Game/Online/Spectator/SpectatorClient.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index f930328846..de5e57a1d0 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -182,9 +182,13 @@ namespace osu.Game.Online.Spectator public void StopWatchingUser(int userId) { - watchingUsers.Remove(userId); - - StopWatchingUserInternal(userId); + // This method is most commonly called via Dispose(), which is asynchronous. + // Todo: This should not be a thing, but requires framework changes. + Schedule(() => + { + watchingUsers.Remove(userId); + StopWatchingUserInternal(userId); + }); } protected abstract Task BeginPlayingInternal(SpectatorState state); From 1848bd902d11f5053237b8aa15636a96931f3867 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 19:51:07 +0900 Subject: [PATCH 243/304] Fix skin editor context menus not dismissing when clicking away --- osu.Game/Skinning/Editor/SkinEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f24b0c71c0..07a94cac7a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Skinning.Editor { [Cached(typeof(SkinEditor))] - public class SkinEditor : FocusedOverlayContainer + public class SkinEditor : VisibilityContainer { public const double TRANSITION_DURATION = 500; From 0f4b502fdf8c1f9651d0b81dc6828f8358826d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 20:09:22 +0900 Subject: [PATCH 244/304] Add missing xmldoc --- osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs index aa83b0567b..46eda06294 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs @@ -5,6 +5,9 @@ using System; namespace osu.Game.Graphics.UserInterface { + /// + /// A ternary state menu item which will always set the item to true on click, even if already true. + /// public class TernaryStateRadioMenuItem : TernaryStateMenuItem { /// From c48b5eebdd7346cbea7b37c727f925f12e173c37 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 20 May 2021 15:45:39 +0300 Subject: [PATCH 245/304] Don't reload the context when clicking selected year button --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 232b995cd6..b07c9924b9 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -109,7 +109,11 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = isCurrent ? Color4.White : colourProvider.Light2; HoverColour = isCurrent ? Color4.White : colourProvider.Light1; - Action = () => overlay?.ShowYear(Year); + Action = () => + { + if (!isCurrent) + overlay?.ShowYear(Year); + }; } } } From 40ca94cd7b9e16d32b81b199e7a24f22f191d6ce Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 20 May 2021 16:04:51 +0300 Subject: [PATCH 246/304] Fix incorrect year being passed on first load --- osu.Game/Overlays/NewsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 510cdba020..af3fa9c3b0 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays loadArticle(article.NewValue); } - private void loadFrontPage(int year = 0) + private void loadFrontPage(int? year = null) { beginLoading(); From 092d0f9b7678a075011029b3300a07b5d111f70f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 01:20:18 +0700 Subject: [PATCH 247/304] add breadcrumb header test scene --- .../TestSceneBreadcrumbControlHeader.cs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs new file mode 100644 index 0000000000..1439c65a14 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs @@ -0,0 +1,86 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneBreadcrumbControlHeader : OsuTestScene + { + private static readonly string[] items = { "first", "second", "third", "fourth", "fifth" }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red); + + private TestHeader header; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = header = new TestHeader + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + [Test] + public void TestAddAndRemoveItem() + { + foreach (var item in items.Skip(1)) + AddStep($"Add {item} item", () => header.AddItem(item)); + + foreach (var item in items.Reverse().SkipLast(3)) + AddStep($"Remove {item} item", () => header.RemoveItem(item)); + + AddStep($"Clear item", () => header.ClearItem()); + + foreach (var item in items) + AddStep($"Add {item} item", () => header.AddItem(item)); + + foreach (var item in items) + AddStep($"Remove {item} item", () => header.RemoveItem(item)); + } + + private class TestHeader : BreadcrumbControlOverlayHeader + { + public TestHeader() + { + TabControl.AddItem(items[0]); + Current.Value = items[0]; + } + + public void AddItem(string value) + { + TabControl.AddItem(value); + Current.Value = TabControl.Items.LastOrDefault(); + } + + public void RemoveItem(string value) + { + TabControl.RemoveItem(value); + Current.Value = TabControl.Items.LastOrDefault(); + } + + public void ClearItem() + { + TabControl.Clear(); + Current.Value = null; + } + + protected override OverlayTitle CreateTitle() => new TestTitle(); + } + + private class TestTitle : OverlayTitle + { + public TestTitle() + { + Title = "Test Title"; + } + } + } +} From 236124496d208c9bcf554ee36ead97170f5705ce Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 01:20:38 +0700 Subject: [PATCH 248/304] add missing accent colour in control tab item --- osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs index 81315f9638..443b3dcf01 100644 --- a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs +++ b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs @@ -26,7 +26,10 @@ namespace osu.Game.Overlays AccentColour = colourProvider.Light2; } - protected override TabItem CreateTabItem(string value) => new ControlTabItem(value); + protected override TabItem CreateTabItem(string value) => new ControlTabItem(value) + { + AccentColour = AccentColour, + }; private class ControlTabItem : BreadcrumbTabItem { From b521405ec8f06393b2b1d841dd5ea4a7c148ae66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 May 2021 20:46:18 +0200 Subject: [PATCH 249/304] Trim redundant string interpolation --- .../Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs index 1439c65a14..45868b2872 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface foreach (var item in items.Reverse().SkipLast(3)) AddStep($"Remove {item} item", () => header.RemoveItem(item)); - AddStep($"Clear item", () => header.ClearItem()); + AddStep("Clear item", () => header.ClearItem()); foreach (var item in items) AddStep($"Add {item} item", () => header.AddItem(item)); From f35a07fee729586abf7723a742a146164c866fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 May 2021 20:47:50 +0200 Subject: [PATCH 250/304] Rename method for better comprehension --- .../Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs index 45868b2872..90c3e142df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface foreach (var item in items.Reverse().SkipLast(3)) AddStep($"Remove {item} item", () => header.RemoveItem(item)); - AddStep("Clear item", () => header.ClearItem()); + AddStep("Clear items", () => header.ClearItems()); foreach (var item in items) AddStep($"Add {item} item", () => header.AddItem(item)); @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface Current.Value = TabControl.Items.LastOrDefault(); } - public void ClearItem() + public void ClearItems() { TabControl.Clear(); Current.Value = null; From 895eb14c5ae222877b49cd322409f0ce3736f927 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 14:09:30 +0900 Subject: [PATCH 251/304] Forcefully end playing to fix test failures --- osu.Game/Online/Spectator/SpectatorClient.cs | 3 +++ osu.Game/Screens/Play/Player.cs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index cb98b01bed..3a29e73b8f 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -174,6 +174,9 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { + if (!IsPlaying) + return; + IsPlaying = false; currentBeatmap = null; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6317a41bec..39f9e2d388 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -22,6 +22,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; +using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Replays; using osu.Game.Rulesets; @@ -93,6 +94,9 @@ namespace osu.Game.Screens.Play [Resolved] private MusicController musicController { get; set; } + [Resolved] + private SpectatorClient spectatorClient { get; set; } + private Sample sampleRestart; public BreakOverlay BreakOverlay; @@ -882,6 +886,11 @@ namespace osu.Game.Screens.Play return true; } + // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. + // To resolve test failures, forcefully end playing synchronously when this screen exits. + // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. + spectatorClient.EndPlaying(); + // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. (GameplayClockContainer as MasterGameplayClockContainer)?.StopUsingBeatmapClock(); From c00e6e29a6f2c32da0ae882f98344afb94770812 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 14:21:56 +0900 Subject: [PATCH 252/304] Remove `static` usage --- .../Edit/Blueprints/HitPlacementBlueprint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index 17e7fb81f6..0d0fd136a7 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -14,10 +14,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { private readonly HitPiece piece; - private static Hit hit; + public new Hit HitObject => (Hit)base.HitObject; public HitPlacementBlueprint() - : base(hit = new Hit()) + : base(new Hit()) { InternalChild = piece = new HitPiece { @@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints switch (e.Button) { case MouseButton.Left: - hit.Type = HitType.Centre; + HitObject.Type = HitType.Centre; EndPlacement(true); return true; case MouseButton.Right: - hit.Type = HitType.Rim; + HitObject.Type = HitType.Rim; EndPlacement(true); return true; } From 40c8378d81a67b944c63d54a86b413ab70af7cb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 14:37:22 +0900 Subject: [PATCH 253/304] Fix type-to-sample mapping being applied too late --- .../Objects/Drawables/DrawableHit.cs | 28 +---------------- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 31 ++++++++++++++++++- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 38cda69a46..948d2c2f69 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -55,16 +55,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables type.BindValueChanged(_ => { updateActionsFromType(); - - // will overwrite samples, should only be called on subsequent changes - // after the initial application. - updateSamplesFromTypeChange(); - RecreatePieces(); - }); - - // action update also has to happen immediately on application. - updateActionsFromType(); + }, true); base.OnApply(); } @@ -92,24 +84,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; } - private void updateSamplesFromTypeChange() - { - var rimSamples = getRimSamples(); - - bool isRimType = HitObject.Type == HitType.Rim; - - if (isRimType != rimSamples.Any()) - { - if (isRimType) - HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); - else - { - foreach (var sample in rimSamples) - HitObject.Samples.Remove(sample); - } - } - } - private void updateActionsFromType() { HitActions = diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 1b51288605..f4a66c39a8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,7 +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.Linq; using osu.Framework.Bindables; +using osu.Game.Audio; namespace osu.Game.Rulesets.Taiko.Objects { @@ -15,9 +17,36 @@ namespace osu.Game.Rulesets.Taiko.Objects public HitType Type { get => TypeBindable.Value; - set => TypeBindable.Value = value; + set + { + TypeBindable.Value = value; + updateSamplesFromType(); + } } + private void updateSamplesFromType() + { + var rimSamples = getRimSamples(); + + bool isRimType = Type == HitType.Rim; + + if (isRimType != rimSamples.Any()) + { + if (isRimType) + Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + else + { + foreach (var sample in rimSamples) + Samples.Remove(sample); + } + } + } + + /// + /// Returns an array of any samples which would cause this object to be a "rim" type hit. + /// + private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime }; public class StrongNestedHit : StrongNestedHitObject From f9d51656b6dff672eab55e46eb1bad287d70b84e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 15:02:36 +0900 Subject: [PATCH 254/304] Fix scaling of rotated items not behaving in an understandable way --- .../Skinning/Editor/SkinSelectionHandler.cs | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2f9611ba65..accb65483b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -37,40 +38,44 @@ namespace osu.Game.Skinning.Editor adjustScaleFromAnchor(ref scale, anchor); - var selectionQuad = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => - b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + // the selection quad is always upright, so use an AABB rect to make mutating the values easier. + var selectionRect = GetSurroundingQuad(SelectedBlueprints.SelectMany(b => + b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())).AABBFloat; - // the selection quad is always upright, so use a rect to make mutating the values easier. - var adjustedRect = selectionQuad.AABBFloat; + // copy to mutate, as we will need to compare to the original later on. + var adjustedRect = selectionRect; - // for now aspect lock scale adjustments that occur at corners. - if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) - scale.Y = scale.X / selectionQuad.Width * selectionQuad.Height; + // first, remove any scale axis we are not interested in. + if (anchor.HasFlagFast(Anchor.x1)) scale.X = 0; + if (anchor.HasFlagFast(Anchor.y1)) scale.Y = 0; - if (anchor.HasFlagFast(Anchor.x0)) + bool shouldAspectLock = + // for now aspect lock scale adjustments that occur at corners.. + (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) + // ..or if any of the selection have been rotated. + // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). + || SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0)); + + if (shouldAspectLock) { - adjustedRect.X -= scale.X; - adjustedRect.Width += scale.X; - } - else if (anchor.HasFlagFast(Anchor.x2)) - { - adjustedRect.Width += scale.X; + if (anchor.HasFlagFast(Anchor.x1)) + // if dragging from the horizontal centre, only a vertical component is available. + scale.X = scale.Y / selectionRect.Height * selectionRect.Width; + else + // in all other cases (arbitrarily) use the horizontal component for aspect lock. + scale.Y = scale.X / selectionRect.Width * selectionRect.Height; } - if (anchor.HasFlagFast(Anchor.y0)) - { - adjustedRect.Y -= scale.Y; - adjustedRect.Height += scale.Y; - } - else if (anchor.HasFlagFast(Anchor.y2)) - { - adjustedRect.Height += scale.Y; - } + if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; + if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; - // scale adjust should match that of the quad itself. + adjustedRect.Width += scale.X; + adjustedRect.Height += scale.Y; + + // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( - adjustedRect.Width / selectionQuad.Width, - adjustedRect.Height / selectionQuad.Height + adjustedRect.Width / selectionRect.Width, + adjustedRect.Height / selectionRect.Height ); foreach (var b in SelectedBlueprints) @@ -82,8 +87,8 @@ namespace osu.Game.Skinning.Editor var relativePositionInOriginal = new Vector2( - (screenPosition.X - selectionQuad.TopLeft.X) / selectionQuad.Width, - (screenPosition.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height + (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width, + (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height ); var newPositionInAdjusted = new Vector2( From 0d575f572866e84421829b7002ef0b695ad4e59f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 15:06:53 +0900 Subject: [PATCH 255/304] Remove incorrect (and unintended) modulus logic --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index accb65483b..0b6d9222ce 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning.Editor (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) // ..or if any of the selection have been rotated. // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - || SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0)); + || SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0)); if (shouldAspectLock) { From fbe4d7e03c2d9e8aca399adb1077f2bf2a47b8b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 15:41:31 +0900 Subject: [PATCH 256/304] Improve code quality around cursor and upwards passing of response data --- .../Overlays/News/Displays/ArticleListing.cs | 30 +++++++------------ osu.Game/Overlays/NewsOverlay.cs | 6 ++-- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index e713b3de84..b49326a1f1 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.News.Displays @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.News.Displays /// public class ArticleListing : CompositeDrawable { - public Action ResponseReceived; + public Action SidebarMetadataUpdated; [Resolved] private IAPIProvider api { get; set; } @@ -98,34 +99,23 @@ namespace osu.Game.Overlays.News.Displays private CancellationTokenSource cancellationToken; - private bool initialLoad = true; - private void onSuccess(GetNewsResponse response) { cancellationToken?.Cancel(); + // only needs to be updated on the initial load, as the content won't change during pagination. + if (lastCursor == null) + SidebarMetadataUpdated?.Invoke(response.SidebarMetadata); + + // store cursor for next pagination request. lastCursor = response.Cursor; - var flow = new FillFlowContainer + LoadComponentsAsync(response.NewsPosts.Select(p => new NewsCard(p)).ToList(), loaded => { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = response.NewsPosts.Select(p => new NewsCard(p)).ToList() - }; + content.AddRange(loaded); - LoadComponentAsync(flow, loaded => - { - content.Add(loaded); showMore.IsLoading = false; - showMore.Alpha = lastCursor == null ? 0 : 1; - - if (initialLoad) - { - ResponseReceived?.Invoke(response); - initialLoad = false; - } + showMore.Alpha = response.Cursor != null ? 1 : 0; }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index af3fa9c3b0..dd6de40ecb 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -133,11 +133,11 @@ namespace osu.Game.Overlays Header.SetFrontPage(); var page = new ArticleListing(year); - page.ResponseReceived += r => + page.SidebarMetadataUpdated += metadata => Schedule(() => { - sidebar.Metadata.Value = r.SidebarMetadata; + sidebar.Metadata.Value = metadata; Loading.Hide(); - }; + }); LoadDisplay(page); } From 2fdf8aa1aa49da528b1fb3d7811a957435b7be3d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 15:57:31 +0900 Subject: [PATCH 257/304] Add update thread assertions --- osu.Game/Online/Spectator/SpectatorClient.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index de5e57a1d0..b90fec09d6 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; @@ -144,6 +145,8 @@ namespace osu.Game.Online.Spectator public void BeginPlaying(GameplayBeatmap beatmap, Score score) { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (IsPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); @@ -172,6 +175,8 @@ namespace osu.Game.Online.Spectator public void WatchUser(int userId) { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (watchingUsers.Contains(userId)) return; @@ -219,6 +224,8 @@ namespace osu.Game.Online.Spectator public void HandleFrame(ReplayFrame frame) { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (frame is IConvertibleReplayFrame convertible) pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); From 7f712a4d04b64b4f9801208048f30f2a272b0a4e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 15:57:39 +0900 Subject: [PATCH 258/304] Fix EndPlaying potentially doing cross-thread mutation --- osu.Game/Online/Spectator/SpectatorClient.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index b90fec09d6..48e2528528 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -167,10 +167,15 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - IsPlaying = false; - currentBeatmap = null; + // This method is most commonly called via Dispose(), which is asynchronous. + // Todo: This should not be a thing, but requires framework changes. + Schedule(() => + { + IsPlaying = false; + currentBeatmap = null; - EndPlayingInternal(currentState); + EndPlayingInternal(currentState); + }); } public void WatchUser(int userId) From 7c59fb37f10874db49102bf4e46e08406a385f60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 16:00:58 +0900 Subject: [PATCH 259/304] Move check into callback --- osu.Game/Online/Spectator/SpectatorClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 9f270a8345..0067a55fd8 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -167,13 +167,13 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - if (!IsPlaying) - return; - // This method is most commonly called via Dispose(), which is asynchronous. // Todo: This should not be a thing, but requires framework changes. Schedule(() => { + if (!IsPlaying) + return; + IsPlaying = false; currentBeatmap = null; From a5ca736e3729d7a57f414834634531d4c8372c28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 16:10:48 +0900 Subject: [PATCH 260/304] Fix `RecreatePieces` being called more than once --- .../Objects/Drawables/DrawableHit.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 948d2c2f69..f2b1284a95 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -52,15 +52,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void OnApply() { type.BindTo(HitObject.TypeBindable); - type.BindValueChanged(_ => - { - updateActionsFromType(); - RecreatePieces(); - }, true); + // this doesn't need to be run inline as RecreatePieces is called by the base call below. + type.BindValueChanged(_ => RecreatePieces()); base.OnApply(); } + protected override void RecreatePieces() + { + updateActionsFromType(); + base.RecreatePieces(); + } + protected override void OnFree() { base.OnFree(); From 7bc8a4bb5fa9e65937340f06560d5ab27ad5ac80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 16:15:27 +0900 Subject: [PATCH 261/304] Apply same logic changes to `IsStrong` status --- .../Objects/Drawables/DrawableHit.cs | 9 ----- .../DrawableTaikoStrongableHitObject.cs | 36 ++----------------- .../Objects/TaikoStrongableHitObject.cs | 26 +++++++++++++- 3 files changed, 27 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index f2b1284a95..5cb2024bb7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -78,15 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables validActionPressed = pressHandledThisFrame = false; } - private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); - - protected override void LoadSamples() - { - base.LoadSamples(); - - type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; - } - private void updateActionsFromType() { HitActions = diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs index 4f1523eb3f..b4acfa9968 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.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.Linq; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Game.Audio; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -29,14 +27,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void OnApply() { isStrong.BindTo(HitObject.IsStrongBindable); - isStrong.BindValueChanged(_ => - { - // will overwrite samples, should only be called on subsequent changes - // after the initial application. - updateSamplesFromStrong(); - - RecreatePieces(); - }); + // this doesn't need to be run inline as RecreatePieces is called by the base call below. + isStrong.BindValueChanged(_ => RecreatePieces()); base.OnApply(); } @@ -50,30 +42,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables isStrong.UnbindEvents(); } - private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); - - protected override void LoadSamples() - { - base.LoadSamples(); - isStrong.Value = getStrongSamples().Any(); - } - - private void updateSamplesFromStrong() - { - var strongSamples = getStrongSamples(); - - if (isStrong.Value != strongSamples.Any()) - { - if (isStrong.Value) - HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); - else - { - foreach (var sample in strongSamples) - HitObject.Samples.Remove(sample); - } - } - } - protected override void RecreatePieces() { base.RecreatePieces(); diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index fcd055bcec..cac56d1269 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using System.Threading; using osu.Framework.Bindables; +using osu.Game.Audio; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Objects @@ -31,9 +33,31 @@ namespace osu.Game.Rulesets.Taiko.Objects public bool IsStrong { get => IsStrongBindable.Value; - set => IsStrongBindable.Value = value; + set + { + IsStrongBindable.Value = value; + updateSamplesFromStrong(); + } } + private void updateSamplesFromStrong() + { + var strongSamples = getStrongSamples(); + + if (IsStrongBindable.Value != strongSamples.Any()) + { + if (IsStrongBindable.Value) + Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + else + { + foreach (var sample in strongSamples) + Samples.Remove(sample); + } + } + } + + private HitSampleInfo[] getStrongSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); From 0bcd0cda6b1ed7facd0264fae35b133bff7f2f5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 16:41:39 +0900 Subject: [PATCH 262/304] Fix taiko drawable hit content not correctly being removed on regeneration --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 6041eccb51..6a8d8a611c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -137,7 +137,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE); - MainPiece?.Expire(); + if (MainPiece != null) + Content.Remove(MainPiece); + Content.Add(MainPiece = CreateMainPiece()); } From 6471ce902d44fce94676f7a8a73ee4e960a07d40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 16:45:28 +0900 Subject: [PATCH 263/304] Run `RecreatePieces` using `AddOnce` to avoid multiple unnecessary calls --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs | 2 +- .../Objects/Drawables/DrawableTaikoStrongableHitObject.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 5cb2024bb7..1e9fc187eb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { type.BindTo(HitObject.TypeBindable); // this doesn't need to be run inline as RecreatePieces is called by the base call below. - type.BindValueChanged(_ => RecreatePieces()); + type.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces)); base.OnApply(); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs index b4acfa9968..70d4371e99 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { isStrong.BindTo(HitObject.IsStrongBindable); // this doesn't need to be run inline as RecreatePieces is called by the base call below. - isStrong.BindValueChanged(_ => RecreatePieces()); + isStrong.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces)); base.OnApply(); } From 0c504c3b7d0adad741bf4d6e0fe2f25538644e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 17:24:22 +0900 Subject: [PATCH 264/304] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 90d131b117..57550cfb93 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 587bdaf622..1e3b77cd70 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7ba7a554d6..a2a9ac35fc 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 8085a5420580065222be24b465fefcd26b1f60ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 17:28:25 +0900 Subject: [PATCH 265/304] Add test coverage of different grade types to `TestSceneResultsScreen` --- .../Visual/Ranking/TestSceneAccuracyCircle.cs | 91 +++---------------- .../Visual/Ranking/TestSceneResultsScreen.cs | 36 +++++--- 2 files changed, 35 insertions(+), 92 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 1e87893f39..f305b7255e 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -22,82 +22,17 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneAccuracyCircle : OsuTestScene { - [Test] - public void TestLowDRank() + [TestCase(0.2, ScoreRank.D)] + [TestCase(0.5, ScoreRank.D)] + [TestCase(0.75, ScoreRank.C)] + [TestCase(0.85, ScoreRank.B)] + [TestCase(0.925, ScoreRank.A)] + [TestCase(0.975, ScoreRank.S)] + [TestCase(0.9999, ScoreRank.S)] + [TestCase(1, ScoreRank.X)] + public void TestRank(double accuracy, ScoreRank rank) { - var score = createScore(); - score.Accuracy = 0.2; - score.Rank = ScoreRank.D; - - addCircleStep(score); - } - - [Test] - public void TestDRank() - { - var score = createScore(); - score.Accuracy = 0.5; - score.Rank = ScoreRank.D; - - addCircleStep(score); - } - - [Test] - public void TestCRank() - { - var score = createScore(); - score.Accuracy = 0.75; - score.Rank = ScoreRank.C; - - addCircleStep(score); - } - - [Test] - public void TestBRank() - { - var score = createScore(); - score.Accuracy = 0.85; - score.Rank = ScoreRank.B; - - addCircleStep(score); - } - - [Test] - public void TestARank() - { - var score = createScore(); - score.Accuracy = 0.925; - score.Rank = ScoreRank.A; - - addCircleStep(score); - } - - [Test] - public void TestSRank() - { - var score = createScore(); - score.Accuracy = 0.975; - score.Rank = ScoreRank.S; - - addCircleStep(score); - } - - [Test] - public void TestAlmostSSRank() - { - var score = createScore(); - score.Accuracy = 0.9999; - score.Rank = ScoreRank.S; - - addCircleStep(score); - } - - [Test] - public void TestSSRank() - { - var score = createScore(); - score.Accuracy = 1; - score.Rank = ScoreRank.X; + var score = createScore(accuracy, rank); addCircleStep(score); } @@ -129,7 +64,7 @@ namespace osu.Game.Tests.Visual.Ranking }; }); - private ScoreInfo createScore() => new ScoreInfo + private ScoreInfo createScore(double accuracy, ScoreRank rank) => new ScoreInfo { User = new User { @@ -139,9 +74,9 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, TotalScore = 2845370, - Accuracy = 0.95, + Accuracy = accuracy, MaxCombo = 999, - Rank = ScoreRank.S, + Rank = rank, Date = DateTimeOffset.Now, Statistics = { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index b2be7cdf88..ba6b6bd529 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -29,13 +29,8 @@ namespace osu.Game.Tests.Visual.Ranking [TestFixture] public class TestSceneResultsScreen : OsuManualInputManagerTestScene { - private BeatmapManager beatmaps; - - [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) - { - this.beatmaps = beatmaps; - } + [Resolved] + private BeatmapManager beatmaps { get; set; } protected override void LoadComplete() { @@ -46,10 +41,6 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); } - private TestResultsScreen createResultsScreen() => new TestResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - - private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - [Test] public void TestResultsWithoutPlayer() { @@ -69,12 +60,25 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay not present", () => screen.RetryOverlay == null); } - [Test] - public void TestResultsWithPlayer() + [TestCase(0.2, ScoreRank.D)] + [TestCase(0.5, ScoreRank.D)] + [TestCase(0.75, ScoreRank.C)] + [TestCase(0.85, ScoreRank.B)] + [TestCase(0.925, ScoreRank.A)] + [TestCase(0.975, ScoreRank.S)] + [TestCase(0.9999, ScoreRank.S)] + [TestCase(1, ScoreRank.X)] + public void TestResultsWithPlayer(double accuracy, ScoreRank rank) { TestResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) + { + Accuracy = accuracy, + Rank = rank + }; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score))); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddAssert("retry overlay present", () => screen.RetryOverlay != null); } @@ -232,6 +236,10 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("download button is enabled", () => screen.ChildrenOfType().Last().Enabled.Value); } + private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? new TestScoreInfo(new OsuRuleset().RulesetInfo)); + + private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + private class TestResultsContainer : Container { [Cached(typeof(Player))] From 41c4afb3d592e05ff1ab62003736e3716c7be856 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 17:46:25 +0900 Subject: [PATCH 266/304] Restore path specification to `"."` for consistency --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index dcbfbf1332..5e975de77c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -65,7 +65,7 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - protected override string ImportFromStablePath => string.Empty; + protected override string ImportFromStablePath => "."; protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage(); From abc96057b2cf50e02e0ec939645f6421684495d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 17:55:46 +0900 Subject: [PATCH 267/304] Remove relative height specification and use constant height --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 ++- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 754b260bf0..e31e307d4d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -96,7 +96,8 @@ namespace osu.Game.Overlays.Mods Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774"); Waves.FourthWaveColour = Color4Extensions.FromHex(@"003a4e"); - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + Height = HEIGHT; Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING }; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 7395b346a4..a53e253581 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -72,8 +72,8 @@ namespace osu.Game.Screens.OnlinePlay.Match Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Depth = float.MinValue, - RelativeSizeAxes = Axes.Both, - Height = 0.7f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, Child = userModsSelectOverlay = new UserModSelectOverlay { From 0acf4cf85c861c7e144dee9ab1d0d3143dbb10f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 18:44:15 +0900 Subject: [PATCH 268/304] Translate remaining `ButtonSystem` strings and rename to match class name --- ...{MainMenu.ja.resx => ButtonSystem.ja.resx} | 0 .../{MainMenu.resx => ButtonSystem.resx} | 0 osu.Game/Localisation/ButtonSystemStrings.cs | 44 +++++++++++++++++++ osu.Game/Localisation/MainMenuStrings.cs | 24 ---------- osu.Game/Screens/Menu/ButtonSystem.cs | 14 +++--- 5 files changed, 51 insertions(+), 31 deletions(-) rename osu.Game/Localisation/{MainMenu.ja.resx => ButtonSystem.ja.resx} (100%) rename osu.Game/Localisation/{MainMenu.resx => ButtonSystem.resx} (100%) create mode 100644 osu.Game/Localisation/ButtonSystemStrings.cs delete mode 100644 osu.Game/Localisation/MainMenuStrings.cs diff --git a/osu.Game/Localisation/MainMenu.ja.resx b/osu.Game/Localisation/ButtonSystem.ja.resx similarity index 100% rename from osu.Game/Localisation/MainMenu.ja.resx rename to osu.Game/Localisation/ButtonSystem.ja.resx diff --git a/osu.Game/Localisation/MainMenu.resx b/osu.Game/Localisation/ButtonSystem.resx similarity index 100% rename from osu.Game/Localisation/MainMenu.resx rename to osu.Game/Localisation/ButtonSystem.resx diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs new file mode 100644 index 0000000000..1d26a73360 --- /dev/null +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class ButtonSystemStrings + { + private const string prefix = @"osu.Game.Localisation.ButtonSystem"; + + /// + /// "solo" + /// + public static LocalisableString Solo => new TranslatableString(getKey(@"solo"), @"solo"); + + /// + /// "multi" + /// + public static LocalisableString Multi => new TranslatableString(getKey(@"multi"), @"multi"); + + /// + /// "playlists" + /// + public static LocalisableString Playlists => new TranslatableString(getKey(@"playlists"), @"playlists"); + + /// + /// "play" + /// + public static LocalisableString Play => new TranslatableString(getKey(@"play"), @"play"); + + /// + /// "edit" + /// + public static LocalisableString Edit => new TranslatableString(getKey(@"edit"), @"edit"); + + /// + /// "browse" + /// + public static LocalisableString Browse => new TranslatableString(getKey(@"browse"), @"browse"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Localisation/MainMenuStrings.cs b/osu.Game/Localisation/MainMenuStrings.cs deleted file mode 100644 index fd9647467a..0000000000 --- a/osu.Game/Localisation/MainMenuStrings.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Localisation; - -namespace osu.Game.Localisation -{ - public static class MainMenuStrings - { - private const string prefix = "osu.Game.Localisation.MainMenu"; - - /// - /// "solo" - /// - public static LocalisableString Solo => new TranslatableString(getKey("solo"), "solo"); - - /// - /// "multi" - /// - public static LocalisableString Multi => new TranslatableString(getKey("multi"), "multi"); - - private static string getKey(string key) => $"{prefix}:{key}"; - } -} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index ff5ad37b9d..c60d1bd4e0 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -125,17 +125,17 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IdleTracker idleTracker, GameHost host, LocalisationManager strings) { - buttonsPlay.Add(new Button(MainMenuStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new Button(MainMenuStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); - buttonsPlay.Add(new Button(@"playlists", @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); + buttonsPlay.Add(new Button(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new Button(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); + buttonsPlay.Add(new Button(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new Button(@"edit", @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); - buttonsTopLevel.Add(new Button(@"browse", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D)); + buttonsTopLevel.Add(new Button(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); + buttonsTopLevel.Add(new Button(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new Button(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D)); if (host.CanExit) - buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); + buttonsTopLevel.Add(new Button("exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); From bf4db60ef4b2ec73fdc114ad07716582027373dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 18:48:42 +0900 Subject: [PATCH 269/304] Remove placeholder translations --- osu.Game/Localisation/Common.ja.resx | 17 ----------------- osu.Game/Localisation/Common.resx | 17 ----------------- 2 files changed, 34 deletions(-) delete mode 100644 osu.Game/Localisation/Common.ja.resx delete mode 100644 osu.Game/Localisation/Common.resx diff --git a/osu.Game/Localisation/Common.ja.resx b/osu.Game/Localisation/Common.ja.resx deleted file mode 100644 index 174751c455..0000000000 --- a/osu.Game/Localisation/Common.ja.resx +++ /dev/null @@ -1,17 +0,0 @@ - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - やめとくわ - - diff --git a/osu.Game/Localisation/Common.resx b/osu.Game/Localisation/Common.resx deleted file mode 100644 index f63fb90086..0000000000 --- a/osu.Game/Localisation/Common.resx +++ /dev/null @@ -1,17 +0,0 @@ - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Cancel - - From cd3f5433942133703acaa0822b9d444efc9218ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 May 2021 02:32:55 +0900 Subject: [PATCH 270/304] Add LocalisationAnalyser package and tools --- .config/dotnet-tools.json | 8 +++++++- osu.Game/osu.Game.csproj | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 58c24181d3..54d9ff3077 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -31,6 +31,12 @@ "commands": [ "CodeFileSanity" ] + }, + "ppy.localisationanalyser.tools": { + "version": "2021.521.1", + "commands": [ + "localisation" + ] } } -} \ No newline at end of file +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 587bdaf622..385aa5c7e1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,6 +29,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 2fc53017fc11c428a552a5af273487a8147810d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 May 2021 13:46:09 +0900 Subject: [PATCH 271/304] Generate initial resx files --- osu.Game/Localisation/ButtonSystem.resx | 94 ++++++++++++++++++++---- osu.Game/Localisation/Chat.resx | 67 +++++++++++++++++ osu.Game/Localisation/Common.resx | 64 ++++++++++++++++ osu.Game/Localisation/Notifications.resx | 67 +++++++++++++++++ osu.Game/Localisation/NowPlaying.resx | 67 +++++++++++++++++ osu.Game/Localisation/Settings.resx | 67 +++++++++++++++++ 6 files changed, 410 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Localisation/Chat.resx create mode 100644 osu.Game/Localisation/Common.resx create mode 100644 osu.Game/Localisation/Notifications.resx create mode 100644 osu.Game/Localisation/NowPlaying.resx create mode 100644 osu.Game/Localisation/Settings.resx diff --git a/osu.Game/Localisation/ButtonSystem.resx b/osu.Game/Localisation/ButtonSystem.resx index 845b412d88..1c6f614c46 100644 --- a/osu.Game/Localisation/ButtonSystem.resx +++ b/osu.Game/Localisation/ButtonSystem.resx @@ -1,17 +1,79 @@ + - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - solo - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + solo + + + multi + + + playlists + + + play + + + edit + + + browse + + \ No newline at end of file diff --git a/osu.Game/Localisation/Chat.resx b/osu.Game/Localisation/Chat.resx new file mode 100644 index 0000000000..3762f800c5 --- /dev/null +++ b/osu.Game/Localisation/Chat.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + chat + + + join the real-time discussion + + \ No newline at end of file diff --git a/osu.Game/Localisation/Common.resx b/osu.Game/Localisation/Common.resx new file mode 100644 index 0000000000..8a3ae29c77 --- /dev/null +++ b/osu.Game/Localisation/Common.resx @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + Cancel + + \ No newline at end of file diff --git a/osu.Game/Localisation/Notifications.resx b/osu.Game/Localisation/Notifications.resx new file mode 100644 index 0000000000..d61e5744e4 --- /dev/null +++ b/osu.Game/Localisation/Notifications.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + notifications + + + waiting for 'ya + + \ No newline at end of file diff --git a/osu.Game/Localisation/NowPlaying.resx b/osu.Game/Localisation/NowPlaying.resx new file mode 100644 index 0000000000..71df15e488 --- /dev/null +++ b/osu.Game/Localisation/NowPlaying.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + now playing + + + manage the currently playing track + + \ No newline at end of file diff --git a/osu.Game/Localisation/Settings.resx b/osu.Game/Localisation/Settings.resx new file mode 100644 index 0000000000..1770147dd9 --- /dev/null +++ b/osu.Game/Localisation/Settings.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + + + settings + + + change the way osu! behaves + + \ No newline at end of file From b6db9ef3346cf75a1b3c9fdaefebec7f76cd208e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 May 2021 13:54:52 +0900 Subject: [PATCH 272/304] Fill out Japanese localisation via resx --- osu.Game/Localisation/ButtonSystem.ja.resx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ButtonSystem.ja.resx b/osu.Game/Localisation/ButtonSystem.ja.resx index 20c85110ad..54c956758c 100644 --- a/osu.Game/Localisation/ButtonSystem.ja.resx +++ b/osu.Game/Localisation/ButtonSystem.ja.resx @@ -14,4 +14,19 @@ ソロ - \ No newline at end of file + + プレイリスト + + + 遊ぶ + + + マルチ + + + エディット + + + ブラウズ + + From fb5672814d6e558881d0101fb5a68799aa047fd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 May 2021 13:55:15 +0900 Subject: [PATCH 273/304] Add remaining strings for `ButtonSystem` --- osu.Game/Localisation/ButtonSystem.ja.resx | 6 ++++++ osu.Game/Localisation/ButtonSystem.resx | 9 +++++++++ osu.Game/Localisation/ButtonSystemStrings.cs | 17 ++++++++++++++++- osu.Game/Screens/Menu/ButtonSystem.cs | 6 +++--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/ButtonSystem.ja.resx b/osu.Game/Localisation/ButtonSystem.ja.resx index 54c956758c..02f3e7ce2f 100644 --- a/osu.Game/Localisation/ButtonSystem.ja.resx +++ b/osu.Game/Localisation/ButtonSystem.ja.resx @@ -29,4 +29,10 @@ ブラウズ + + 閉じる + + + 設定 + diff --git a/osu.Game/Localisation/ButtonSystem.resx b/osu.Game/Localisation/ButtonSystem.resx index 1c6f614c46..e8f555b6c3 100644 --- a/osu.Game/Localisation/ButtonSystem.resx +++ b/osu.Game/Localisation/ButtonSystem.resx @@ -76,4 +76,13 @@ browse + + settings + + + back + + + exit + \ No newline at end of file diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs index 1d26a73360..8083f80782 100644 --- a/osu.Game/Localisation/ButtonSystemStrings.cs +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -39,6 +39,21 @@ namespace osu.Game.Localisation /// public static LocalisableString Browse => new TranslatableString(getKey(@"browse"), @"browse"); + /// + /// "settings" + /// + public static LocalisableString Settings => new TranslatableString(getKey(@"settings"), @"settings"); + + /// + /// "back" + /// + public static LocalisableString Back => new TranslatableString(getKey(@"back"), @"back"); + + /// + /// "exit" + /// + public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"exit"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index c60d1bd4e0..a836f7bf09 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -99,8 +99,8 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { - new Button(@"settings", string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) + new Button(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + backButton = new Button(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Menu buttonsTopLevel.Add(new Button(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D)); if (host.CanExit) - buttonsTopLevel.Add(new Button("exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); + buttonsTopLevel.Add(new Button(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); From 7d88a19d7f67bc540646eed0992ba909fd51990b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 May 2021 21:03:40 +0900 Subject: [PATCH 274/304] Remove unnecessary field storage of origin reference --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 7f04252c6b..7d9ed6b46c 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -20,17 +20,9 @@ namespace osu.Game.Skinning.Editor { public class SkinSelectionHandler : SelectionHandler { - private Vector2? referenceOrigin; - [Resolved] private SkinEditor skinEditor { get; set; } - protected override void OnOperationEnded() - { - base.OnOperationEnded(); - referenceOrigin = null; - } - public override bool HandleRotation(float angle) { if (SelectedBlueprints.Count == 1) @@ -42,13 +34,11 @@ namespace osu.Game.Skinning.Editor { var selectionQuad = getSelectionQuad(); - referenceOrigin ??= selectionQuad.Centre; - foreach (var b in SelectedBlueprints) { var drawableItem = (Drawable)b.Item; - drawableItem.Position = drawableItem.Parent.ToLocalSpace(RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, referenceOrigin.Value, angle)) - drawableItem.AnchorPosition; + drawableItem.Position = drawableItem.Parent.ToLocalSpace(RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, selectionQuad.Centre, angle)) - drawableItem.AnchorPosition; drawableItem.Rotation += angle; } } From 2fd0038154082ec3d622aa3ddff0e25ee29684ad Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 22 May 2021 16:42:20 -0700 Subject: [PATCH 275/304] Fix checkmark being hidden after clicking current waveform opacity setting --- osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs index 053c2fa4b0..7e095f526e 100644 --- a/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs +++ b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Edit { private readonly Bindable waveformOpacity; - private readonly Dictionary menuItemLookup = new Dictionary(); + private readonly Dictionary menuItemLookup = new Dictionary(); public WaveformOpacityMenuItem(Bindable waveformOpacity) : base("Waveform opacity") @@ -29,13 +29,13 @@ namespace osu.Game.Screens.Edit waveformOpacity.BindValueChanged(opacity => { foreach (var kvp in menuItemLookup) - kvp.Value.State.Value = kvp.Key == opacity.NewValue; + kvp.Value.State.Value = kvp.Key == opacity.NewValue ? TernaryState.True : TernaryState.False; }, true); } - private ToggleMenuItem createMenuItem(float opacity) + private TernaryStateRadioMenuItem createMenuItem(float opacity) { - var item = new ToggleMenuItem($"{opacity * 100}%", MenuItemType.Standard, _ => updateOpacity(opacity)); + var item = new TernaryStateRadioMenuItem($"{opacity * 100}%", MenuItemType.Standard, _ => updateOpacity(opacity)); menuItemLookup[opacity] = item; return item; } From caa2c5638e2695dae4719613edcdf49e00c57cb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 May 2021 16:46:32 +0900 Subject: [PATCH 276/304] Fix legacy combo counter not accounting for song progress bar --- osu.Game/Skinning/LegacySkin.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7a64f38840..fb9cf47cb7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -338,6 +338,7 @@ namespace osu.Game.Skinning { var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); + var combo = container.OfType().FirstOrDefault(); if (score != null && accuracy != null) { @@ -353,9 +354,12 @@ namespace osu.Game.Skinning hitError.Anchor = Anchor.BottomCentre; hitError.Origin = Anchor.CentreLeft; hitError.Rotation = -90; + } - if (songProgress != null) - hitError.Y -= SongProgress.MAX_HEIGHT; + if (songProgress != null) + { + if (hitError != null) hitError.Y -= SongProgress.MAX_HEIGHT; + if (combo != null) combo.Y -= SongProgress.MAX_HEIGHT; } }) { From bbfd7ea23f622f96bcec0f0d4713068bd1eda920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 May 2021 21:20:08 +0900 Subject: [PATCH 277/304] Ensure `RegenerateAutoplay` is only run once per frame --- osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index 62e2539c2a..8166e6b8ce 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Edit } } - private void updateReplay() => drawableRuleset.RegenerateAutoplay(); + private void updateReplay() => Scheduler.AddOnce(drawableRuleset.RegenerateAutoplay); private void addHitObject(HitObject hitObject) { From 6751d79ce8ef94b465360384c7d8ffbaca84330c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 May 2021 16:21:27 +0300 Subject: [PATCH 278/304] Fix oversight in HUD overlay components top positioning logic --- osu.Game/Screens/Play/HUDOverlay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ab5b01cab6..16285ab035 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -176,10 +176,7 @@ namespace osu.Game.Screens.Play foreach (var element in mainComponents.Components.Cast()) { // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. - if (!element.RelativeSizeAxes.HasFlagFast(Axes.X)) - continue; - - if (element.Anchor.HasFlagFast(Anchor.TopRight)) + if (element.Anchor.HasFlagFast(Anchor.TopRight) || (element.Anchor.HasFlagFast(Anchor.y0) && element.RelativeSizeAxes == Axes.X)) { // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. if (element is LegacyHealthDisplay) From d605b6bb8db010c73a59129d93185466a3ee3a8c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 May 2021 16:22:51 +0300 Subject: [PATCH 279/304] Fix HUD overlay components bottom positioning logic accounting for combo --- osu.Game/Screens/Play/HUDOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 16285ab035..31bb640d17 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -186,7 +186,8 @@ namespace osu.Game.Screens.Play if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y) lowestTopScreenSpace = bottomRight; } - else if (element.Anchor.HasFlagFast(Anchor.y2)) + // and align bottom-right components with the top-edge of the highest bottom-anchored hud element. + else if (element.Anchor.HasFlagFast(Anchor.BottomRight) || (element.Anchor.HasFlagFast(Anchor.y2) && element.RelativeSizeAxes == Axes.X)) { var topLeft = element.ScreenSpaceDrawQuad.TopLeft; if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y) From 593fea0d5fe8ae8a9286a41b967833fab1443c2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 13:14:18 +0900 Subject: [PATCH 280/304] Limit automatically calculated HUD offsets to keep menu items on screen --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 31bb640d17..676d7f10e1 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -196,12 +196,12 @@ namespace osu.Game.Screens.Play } if (lowestTopScreenSpace.HasValue) - topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestTopScreenSpace.Value).Y; + topRightElements.Y = TopScoringElementsHeight = Math.Max(0, ToLocalSpace(lowestTopScreenSpace.Value).Y); else topRightElements.Y = 0; if (highestBottomScreenSpace.HasValue) - bottomRightElements.Y = BottomScoringElementsHeight = -(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y); + bottomRightElements.Y = BottomScoringElementsHeight = -Math.Max(0, (DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y)); else bottomRightElements.Y = 0; } From 83981b692eda95bf0ea97faedfc92381cf2fb370 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 13:44:13 +0900 Subject: [PATCH 281/304] Also handle items exiting bounds on the opposite side --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 676d7f10e1..ffe03815f5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -196,12 +196,12 @@ namespace osu.Game.Screens.Play } if (lowestTopScreenSpace.HasValue) - topRightElements.Y = TopScoringElementsHeight = Math.Max(0, ToLocalSpace(lowestTopScreenSpace.Value).Y); + topRightElements.Y = TopScoringElementsHeight = MathHelper.Clamp(ToLocalSpace(lowestTopScreenSpace.Value).Y, 0, DrawHeight - topRightElements.DrawHeight); else topRightElements.Y = 0; if (highestBottomScreenSpace.HasValue) - bottomRightElements.Y = BottomScoringElementsHeight = -Math.Max(0, (DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y)); + bottomRightElements.Y = BottomScoringElementsHeight = -MathHelper.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - bottomRightElements.DrawHeight); else bottomRightElements.Y = 0; } From 7494ddeef40293f989c412e51438e450dd93997b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 May 2021 14:07:40 +0900 Subject: [PATCH 282/304] Fix DHOs not receiving initial skin changed events --- .../Objects/Drawables/DrawableHitObject.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 86c733c392..cc663c37af 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,13 @@ namespace osu.Game.Rulesets.Objects.Drawables base.AddInternal(Samples = new PausableSkinnableSound()); CurrentSkin = skinSource; - CurrentSkin.SourceChanged += onSkinSourceChanged; + CurrentSkin.SourceChanged += skinSourceChanged; + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + skinChanged(); } protected override void LoadComplete() @@ -495,7 +501,9 @@ namespace osu.Game.Rulesets.Objects.Drawables protected ISkinSource CurrentSkin { get; private set; } - private void onSkinSourceChanged() => Scheduler.AddOnce(() => + private void skinSourceChanged() => Scheduler.AddOnce(skinChanged); + + private void skinChanged() { UpdateComboColour(); @@ -503,7 +511,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (IsLoaded) updateState(State.Value, true); - }); + } protected void UpdateComboColour() { @@ -747,7 +755,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject != null) HitObject.DefaultsApplied -= onDefaultsApplied; - CurrentSkin.SourceChanged -= onSkinSourceChanged; + CurrentSkin.SourceChanged -= skinSourceChanged; } } From 83364285746b651bd8ccd27be11e0aaa127b493b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 15:10:33 +0900 Subject: [PATCH 283/304] Add regression test for spinner sample actually transforming its frequency --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 14 +++++++++++++- .../Objects/Drawables/DrawableSpinner.cs | 6 +++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index f697a77d94..0a7ef443b1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -5,12 +5,14 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Tests @@ -32,6 +34,16 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep($"{term} Small", () => SetContents(() => testSingle(7, autoplay))); } + [Test] + public void TestSpinningSamplePitchShift() + { + AddStep("Add spinner", () => SetContents(() => testSingle(5, true, 4000))); + AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8); + AddUntilStep("Pitch increases", () => getSpinningSample().Frequency.Value > 0.8); + + PausableSkinnableSound getSpinningSample() => drawableSpinner.ChildrenOfType().FirstOrDefault(s => s.Samples.Any(i => i.LookupNames.Any(l => l.Contains("spinnerspin")))); + } + [TestCase(false)] [TestCase(true)] public void TestLongSpinner(bool autoplay) @@ -93,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests { base.Update(); if (auto) - RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * 3)); + RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * 2)); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3a4753761a..19cee61f26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Bindable isSpinning; private bool spinnerFrequencyModulate; + private const float spinning_sample_initial_frequency = 1.0f; + private const float spinning_sample_modulated_base_frequency = 0.5f; + /// /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. /// @@ -106,9 +109,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables isSpinning.BindValueChanged(updateSpinningSample); } - private const float spinning_sample_initial_frequency = 1.0f; - private const float spinning_sample_modulated_base_frequency = 0.5f; - protected override void OnFree() { base.OnFree(); From e5f586f2a6940679a41b87ebe3f58d818226795e Mon Sep 17 00:00:00 2001 From: Firmatorenio Date: Mon, 24 May 2021 13:29:12 +0600 Subject: [PATCH 284/304] fix colour hit error meter not pushing misses when wrong colour note is hit in taiko --- .../Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 0eb2367f73..3a79183d34 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -24,7 +25,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters InternalChild = judgementsFlow = new JudgementFlow(); } - protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset))); + //Taiko-specific: hitting a wrong colour note should result in a miss being pushed. + protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(judgement.Type == HitResult.Miss ? GetColourForHitResult(HitResult.Miss) : GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset))); private class JudgementFlow : FillFlowContainer { From 4fc6ba50b7beaf51c9ca1c22a87cfa7334a65bda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 17:03:54 +0900 Subject: [PATCH 285/304] Fix editor placement ending early if a blueprint becomes alive from a pool Closes https://github.com/ppy/osu/issues/12630. --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index e231f7f648..3e97e15cca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -61,6 +61,8 @@ namespace osu.Game.Screens.Edit.Compose.Components inputManager = GetContainingInputManager(); + Beatmap.HitObjectAdded += hitObjectAdded; + // updates to selected are handled for us by SelectionHandler. NewCombo.BindTo(SelectionHandler.SelectionNewComboState); @@ -259,10 +261,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; - protected override void OnBlueprintAdded(HitObject item) + private void hitObjectAdded(HitObject obj) { - base.OnBlueprintAdded(item); - + // refresh the tool to handle the case of placement completing. refreshTool(); // on successful placement, the new combo button should be reset as this is the most common user interaction. From f8c615049379316b8fce79ac55e34d4a745f140f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 17:11:01 +0900 Subject: [PATCH 286/304] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 57550cfb93..b3842a528d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 87d1707420..ed033b3934 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -33,7 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index a2a9ac35fc..e35b1b5c42 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 3db995c7782c0f643239c97798c9bbc90aafd843 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 May 2021 17:15:57 +0900 Subject: [PATCH 287/304] Fix sliders jumping around the screen on movement --- .../Sliders/SliderSelectionBlueprint.cs | 7 ++++++- .../Objects/Drawables/DrawableSlider.cs | 17 +++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ec97f1fd78..e810d2fe0c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderSelectionBlueprint : OsuSelectionBlueprint { + protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject; + protected SliderBodyPiece BodyPiece { get; private set; } protected SliderCircleOverlay HeadOverlay { get; private set; } protected SliderCircleOverlay TailOverlay { get; private set; } @@ -236,7 +239,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); + // Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions. + public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset) + ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 82b5492de6..0bec33bf77 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects; - private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; + [CanBeNull] + public PlaySliderBody SliderBody => Body.Drawable as PlaySliderBody; public IBindable PathVersion => pathVersion; private readonly Bindable pathVersion = new Bindable(); @@ -215,16 +216,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1); Ball.UpdateProgress(completionProgress); - sliderBody?.UpdateProgress(completionProgress); + SliderBody?.UpdateProgress(completionProgress); foreach (DrawableHitObject hitObject in NestedHitObjects) { - if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(sliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(sliderBody?.SnakedEnd ?? 0)); + if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0)); if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; } - Size = sliderBody?.Size ?? Vector2.Zero; - OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero; + Size = SliderBody?.Size ?? Vector2.Zero; + OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero; if (DrawSize != Vector2.Zero) { @@ -238,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override void OnKilled() { base.OnKilled(); - sliderBody?.RecyclePath(); + SliderBody?.RecyclePath(); } protected override void ApplySkin(ISkinSource skin, bool allowFallback) @@ -324,7 +325,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { case ArmedState.Hit: Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); - if (sliderBody?.SnakingOut.Value == true) + if (SliderBody?.SnakingOut.Value == true) Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear. break; } @@ -332,7 +333,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); private class DefaultSliderBody : PlaySliderBody { From 7961dba1d3dccd79e6bd79b60de5d21f8a396de6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 17:22:55 +0900 Subject: [PATCH 288/304] Reorder `OrderBy` for legibility --- osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index cd8b486f23..23b09e8fb1 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -73,10 +73,11 @@ namespace osu.Game.Input.Bindings else { KeyBindings = store.Query(ruleset?.ID, variant) + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction)) // this ordering is important to ensure that we read entries from the database in the order // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction)).ToList(); + .ToList(); } } } From 57640810b5023025b3b4ac933d35495663de39bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 17:23:09 +0900 Subject: [PATCH 289/304] Ignore certain banned `InputKey`s for gameplay purposes --- osu.Game/Input/KeyBindingStore.cs | 22 +++++++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 9d0cfedc03..1d20f0d2c3 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -16,6 +16,17 @@ namespace osu.Game.Input { public event Action KeyBindingChanged; + /// + /// Keys which should not be allowed for gameplay input purposes. + /// + private static readonly IEnumerable banned_keys = new[] + { + InputKey.MouseWheelDown, + InputKey.MouseWheelLeft, + InputKey.MouseWheelUp, + InputKey.MouseWheelRight + }; + public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) : base(contextFactory, storage) { @@ -103,5 +114,16 @@ namespace osu.Game.Input KeyBindingChanged?.Invoke(); } + + public static bool CheckValidForGameplay(KeyCombination combination) + { + foreach (var key in banned_keys) + { + if (combination.Keys.Contains(key)) + return false; + } + + return true; + } } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index d6f002ea2c..75c3a4661c 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Game.Configuration; +using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; @@ -169,6 +170,13 @@ namespace osu.Game.Rulesets.UI : base(ruleset, variant, unique) { } + + protected override void ReloadMappings() + { + base.ReloadMappings(); + + KeyBindings = KeyBindings.Where(b => KeyBindingStore.CheckValidForGameplay(b.KeyCombination)).ToList(); + } } } From deabce7140fea2922a1916b63f35ce98cbb18f7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 18:40:56 +0900 Subject: [PATCH 290/304] Disallow updating the database to an invalid value --- osu.Game/Input/KeyBindingStore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 1d20f0d2c3..23b0e0c0d0 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -104,6 +104,10 @@ namespace osu.Game.Input using (ContextFactory.GetForWrite()) { var dbKeyBinding = (DatabasedKeyBinding)keyBinding; + + if (dbKeyBinding.RulesetID != null && !CheckValidForGameplay(keyBinding.KeyCombination)) + return; + Refresh(ref dbKeyBinding); if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) From a00f226ab3566028bb2f0590d84caaeeeb32cb55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 18:41:39 +0900 Subject: [PATCH 291/304] Add assert on storing to database --- osu.Game/Input/KeyBindingStore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 23b0e0c0d0..3ef9923487 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Input.Bindings; using osu.Framework.Platform; @@ -105,8 +106,7 @@ namespace osu.Game.Input { var dbKeyBinding = (DatabasedKeyBinding)keyBinding; - if (dbKeyBinding.RulesetID != null && !CheckValidForGameplay(keyBinding.KeyCombination)) - return; + Debug.Assert(dbKeyBinding.RulesetID == null || CheckValidForGameplay(keyBinding.KeyCombination)); Refresh(ref dbKeyBinding); From 471f17547aa12befa1fb5162be18c4a8d03afede Mon Sep 17 00:00:00 2001 From: Firmatorenio Date: Mon, 24 May 2021 16:49:58 +0600 Subject: [PATCH 292/304] switch determining the hit result by offset to getting it from the judgement directly --- .../Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 3a79183d34..e9ccbcdae2 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -25,8 +24,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters InternalChild = judgementsFlow = new JudgementFlow(); } - //Taiko-specific: hitting a wrong colour note should result in a miss being pushed. - protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(judgement.Type == HitResult.Miss ? GetColourForHitResult(HitResult.Miss) : GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset))); + protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(judgement.Type)); private class JudgementFlow : FillFlowContainer { From 02cdd0b2deff3a03ee3a020d6e5a46684b5daa17 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 24 May 2021 19:10:37 +0700 Subject: [PATCH 293/304] use drawable link compiler in markdown link --- .../Markdown/OsuMarkdownLinkText.cs | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index 2efb60d125..f44f818bf0 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -1,48 +1,62 @@ // 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 Markdig.Syntax.Inlines; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; +using osu.Game.Online.Chat; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownLinkText : MarkdownLinkText { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } - private SpriteText spriteText; + protected string Text; + protected string Title; public OsuMarkdownLinkText(string text, LinkInline linkInline) : base(text, linkInline) { + Text = text; + Title = linkInline.Title; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { - spriteText.Colour = colourProvider.Light2; + var text = CreateSpriteText().With(t => t.Text = Text); + InternalChildren = new Drawable[] + { + text, + new OsuMarkdownLinkCompiler(new[] { text }) + { + RelativeSizeAxes = Axes.Both, + Action = OnLinkPressed, + TooltipText = Title ?? Url, + } + }; } - public override SpriteText CreateSpriteText() - { - return spriteText = base.CreateSpriteText(); - } + protected override void OnLinkPressed() => game?.HandleLink(Url); - protected override bool OnHover(HoverEvent e) + private class OsuMarkdownLinkCompiler : DrawableLinkCompiler { - spriteText.Colour = colourProvider.Light1; - return base.OnHover(e); - } + public OsuMarkdownLinkCompiler(IEnumerable parts) + : base(parts) + { + } - protected override void OnHoverLost(HoverLostEvent e) - { - spriteText.Colour = colourProvider.Light2; - base.OnHoverLost(e); + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + } } } } From fe9abd5b8048b029b5c103cf5e35d1ec76bb318f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 May 2021 21:37:05 +0900 Subject: [PATCH 294/304] Upgrade packages and fix ResX files --- .config/dotnet-tools.json | 4 ++-- osu.Game/Localisation/ButtonSystem.resx | 4 ++-- osu.Game/Localisation/Chat.resx | 4 ++-- osu.Game/Localisation/Common.resx | 4 ++-- osu.Game/Localisation/Notifications.resx | 4 ++-- osu.Game/Localisation/NowPlaying.resx | 4 ++-- osu.Game/Localisation/Settings.resx | 4 ++-- osu.Game/osu.Game.csproj | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 54d9ff3077..b51ecb4f7e 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -33,10 +33,10 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2021.521.1", + "version": "2021.524.0", "commands": [ "localisation" ] } } -} +} \ No newline at end of file diff --git a/osu.Game/Localisation/ButtonSystem.resx b/osu.Game/Localisation/ButtonSystem.resx index e8f555b6c3..d72ffff8be 100644 --- a/osu.Game/Localisation/ButtonSystem.resx +++ b/osu.Game/Localisation/ButtonSystem.resx @@ -53,10 +53,10 @@ 2.0 - System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 solo diff --git a/osu.Game/Localisation/Chat.resx b/osu.Game/Localisation/Chat.resx index 3762f800c5..055e351463 100644 --- a/osu.Game/Localisation/Chat.resx +++ b/osu.Game/Localisation/Chat.resx @@ -53,10 +53,10 @@ 2.0 - System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 chat diff --git a/osu.Game/Localisation/Common.resx b/osu.Game/Localisation/Common.resx index 8a3ae29c77..59de16a037 100644 --- a/osu.Game/Localisation/Common.resx +++ b/osu.Game/Localisation/Common.resx @@ -53,10 +53,10 @@ 2.0 - System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Cancel diff --git a/osu.Game/Localisation/Notifications.resx b/osu.Game/Localisation/Notifications.resx index d61e5744e4..08db240ba2 100644 --- a/osu.Game/Localisation/Notifications.resx +++ b/osu.Game/Localisation/Notifications.resx @@ -53,10 +53,10 @@ 2.0 - System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 notifications diff --git a/osu.Game/Localisation/NowPlaying.resx b/osu.Game/Localisation/NowPlaying.resx index 71df15e488..40fda3e25b 100644 --- a/osu.Game/Localisation/NowPlaying.resx +++ b/osu.Game/Localisation/NowPlaying.resx @@ -53,10 +53,10 @@ 2.0 - System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 now playing diff --git a/osu.Game/Localisation/Settings.resx b/osu.Game/Localisation/Settings.resx index 1770147dd9..85c224cedf 100644 --- a/osu.Game/Localisation/Settings.resx +++ b/osu.Game/Localisation/Settings.resx @@ -53,10 +53,10 @@ 2.0 - System.Resources.NetStandard.ResXResourceReader, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.NetStandard.ResXResourceWriter, System.Resources.NetStandard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 settings diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ed033b3934..fa2945db6a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 6f155fbd08500f58ce3629a0b958b8d549546114 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 May 2021 21:54:55 +0900 Subject: [PATCH 295/304] Make inspection a hint --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 4ac796ccd0..62751cebb1 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -120,6 +120,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT From 62b6cadb64eefd23b8b1ffaaf37987bfbf4fd5cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 18:41:51 +0900 Subject: [PATCH 296/304] Ensure settings rows cannot set an invalid value in the first place --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 43942d2d52..9c09b6e7d0 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input; +using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -445,6 +446,9 @@ namespace osu.Game.Overlays.KeyBinding public void UpdateKeyCombination(KeyCombination newCombination) { + if ((KeyBinding as DatabasedKeyBinding)?.RulesetID != null && !KeyBindingStore.CheckValidForGameplay(newCombination)) + return; + KeyBinding.KeyCombination = newCombination; Text.Text = KeyBinding.KeyCombination.ReadableString(); } From 37f6ceef798eb47b5778884bc1d41187ee293e76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 21:56:47 +0900 Subject: [PATCH 297/304] Add test coverage --- .../Settings/TestSceneKeyBindingPanel.cs | 66 +++++++++++++++++++ osu.Game/Overlays/SettingsPanel.cs | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index f495e0fb23..55c5b5b9c2 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.KeyBinding; using osuTK.Input; @@ -28,6 +29,39 @@ namespace osu.Game.Tests.Visual.Settings panel.Show(); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Scroll to top", () => panel.ChildrenOfType().First().ScrollToTop()); + AddWaitStep("wait for scroll", 5); + } + + [Test] + public void TestBindingMouseWheelToNonGameplay() + { + scrollToAndStartBinding("Increase volume"); + AddStep("press k", () => InputManager.Key(Key.K)); + checkBinding("Increase volume", "K"); + + AddStep("click again", () => InputManager.Click(MouseButton.Left)); + AddStep("scroll mouse wheel", () => InputManager.ScrollVerticalBy(1)); + + checkBinding("Increase volume", "Wheel Up"); + } + + [Test] + public void TestBindingMouseWheelToGameplay() + { + scrollToAndStartBinding("Left button"); + AddStep("press k", () => InputManager.Key(Key.Z)); + checkBinding("Left button", "Z"); + + AddStep("click again", () => InputManager.Click(MouseButton.Left)); + AddStep("scroll mouse wheel", () => InputManager.ScrollVerticalBy(1)); + + checkBinding("Left button", "Z"); + } + [Test] public void TestClickTwiceOnClearButton() { @@ -135,5 +169,37 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); } + + private void checkBinding(string name, string keyName) + { + AddAssert($"Check {name} is bound to {keyName}", () => + { + var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name)); + var firstButton = firstRow.ChildrenOfType().First(); + + return firstButton.Text.Text == keyName; + }); + } + + private void scrollToAndStartBinding(string name) + { + KeyBindingRow.KeyButton firstButton = null; + + AddStep($"Scroll to {name}", () => + { + var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name)); + firstButton = firstRow.ChildrenOfType().First(); + + panel.ChildrenOfType().First().ScrollTo(firstButton); + }); + + AddWaitStep("wait for scroll", 5); + + AddStep("click to bind", () => + { + InputManager.MoveMouseTo(firstButton); + InputManager.Click(MouseButton.Left); + }); + } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 8f3274b2b5..f0a11d67b7 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -191,7 +191,7 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } - protected class SettingsSectionsContainer : SectionsContainer + public class SettingsSectionsContainer : SectionsContainer { public SearchContainer SearchContainer; From 2e6d46390152f59efecfdc6a6e43fbfa36d175cf Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 24 May 2021 20:45:47 +0700 Subject: [PATCH 298/304] add test link with title --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index dc41f184f2..931af7bc95 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -86,6 +86,15 @@ _**italic with underscore, bold with asterisk**_"; }); } + [Test] + public void TestLinkWithTitle() + { + AddStep("Add Link with title", () => + { + markdownContainer.Text = "[wikipedia](https://www.wikipedia.org \"The Free Encyclopedia\")"; + }); + } + [Test] public void TestInlineCode() { From 728258d93a243e8c3655ba6655f36232deafa58e Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 25 May 2021 00:29:59 +0700 Subject: [PATCH 299/304] add website root url as document url --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 6facf4e26c..9ecedba59a 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -6,11 +6,13 @@ using Markdig.Extensions.AutoIdentifiers; using Markdig.Extensions.Tables; using Markdig.Extensions.Yaml; using Markdig.Syntax; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; namespace osu.Game.Graphics.Containers.Markdown { @@ -21,6 +23,12 @@ namespace osu.Game.Graphics.Containers.Markdown LineSpacing = 21; } + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + DocumentUrl = api.WebsiteRootUrl; + } + protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) { switch (markdownObject) From 65649e5a6264707f8079e5714931135431851cbf Mon Sep 17 00:00:00 2001 From: kamp Date: Mon, 24 May 2021 21:36:42 +0200 Subject: [PATCH 300/304] Prevent skin editor crash when scaling 0 area drawables Some skinnable drawables can have 0 width or height in certain cases, leading to division by 0 and a crash when the position is updated. --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 99bd22c0bf..7ef07541b4 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -59,6 +59,10 @@ namespace osu.Game.Skinning.Editor // the selection quad is always upright, so use an AABB rect to make mutating the values easier. var selectionRect = getSelectionQuad().AABBFloat; + // If the selection has no area we cannot scale it + if (selectionRect.Area == 0.0) + return false; + // copy to mutate, as we will need to compare to the original later on. var adjustedRect = selectionRect; From 200592114f9e20d0f2ede64a70135f50f0319e00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 14:13:00 +0900 Subject: [PATCH 301/304] Make protected variables private --- .../Containers/Markdown/OsuMarkdownLinkText.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index f44f818bf0..840bf77348 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -16,28 +16,29 @@ namespace osu.Game.Graphics.Containers.Markdown [Resolved(canBeNull: true)] private OsuGame game { get; set; } - protected string Text; - protected string Title; + private readonly string text; + private readonly string title; public OsuMarkdownLinkText(string text, LinkInline linkInline) : base(text, linkInline) { - Text = text; - Title = linkInline.Title; + this.text = text; + title = linkInline.Title; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - var text = CreateSpriteText().With(t => t.Text = Text); + var textDrawable = CreateSpriteText().With(t => t.Text = text); + InternalChildren = new Drawable[] { - text, - new OsuMarkdownLinkCompiler(new[] { text }) + textDrawable, + new OsuMarkdownLinkCompiler(new[] { textDrawable }) { RelativeSizeAxes = Axes.Both, Action = OnLinkPressed, - TooltipText = Title ?? Url, + TooltipText = title ?? Url, } }; } From b36b40cb3430e6bd511390f82c39ba23195dc8cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 15:20:47 +0900 Subject: [PATCH 302/304] Remove unnecessary double specification --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 7ef07541b4..9cca0ba2c7 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -60,7 +60,7 @@ namespace osu.Game.Skinning.Editor var selectionRect = getSelectionQuad().AABBFloat; // If the selection has no area we cannot scale it - if (selectionRect.Area == 0.0) + if (selectionRect.Area == 0) return false; // copy to mutate, as we will need to compare to the original later on. From 04f16c07836e57ea8ae8ef571ef2769c86fba054 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 13:55:16 +0700 Subject: [PATCH 303/304] Set `DocumentUrl` inside `CreateChildDependencies` Co-authored-by: Dean Herbert --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 9ecedba59a..ad11a9625e 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -23,10 +23,14 @@ namespace osu.Game.Graphics.Containers.Markdown LineSpacing = 21; } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { + var api = parent.Get(); + + // needs to be set before the base BDL call executes to avoid invalidating any already populated markdown content. DocumentUrl = api.WebsiteRootUrl; + + return base.CreateChildDependencies(parent); } protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) From e02739a13608866df8391f515b7ab8f31e4d6f6b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 13:57:35 +0700 Subject: [PATCH 304/304] remove unused colour provider --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index 840bf77348..f91a0e40e3 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers.Markdown } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { var textDrawable = CreateSpriteText().With(t => t.Text = text);