From d83abfa7d2cbcfcdb2922f1756a3b2b8fac18ff5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jan 2021 23:29:32 +0300 Subject: [PATCH 001/429] 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/429] 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/429] 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/429] 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/429] 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 623eae15762f4cf796dfbd2fbab26b64a3aaa142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 17:06:01 +0900 Subject: [PATCH 006/429] 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 007/429] 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 008/429] 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 009/429] 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 010/429] 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 011/429] 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 012/429] 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 013/429] 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 014/429] 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 015/429] 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 016/429] 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 017/429] 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 018/429] 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 019/429] 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 020/429] 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 021/429] 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 022/429] 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 023/429] 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 ce8b2c1e379de69697ca880a24be662ae726a120 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 17 May 2021 01:08:02 +0700 Subject: [PATCH 024/429] add WikiMarkdownContainer --- .../Wiki/Markdown/WikiMarkdownContainer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs new file mode 100644 index 0000000000..ac128d79dc --- /dev/null +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.Containers.Markdown; + +namespace osu.Game.Overlays.Wiki.Markdown +{ + public class WikiMarkdownContainer : OsuMarkdownContainer + { + public string CurrentPath + { + set => Schedule(() => DocumentUrl += $"wiki/{value}"); + } + } +} From a38c33841a8b4cb46f07acc16ad5bdc79b635a58 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 16:30:41 +0700 Subject: [PATCH 025/429] add test scene wiki container --- .../Online/TestSceneWikiMarkdownContainer.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs new file mode 100644 index 0000000000..fd4c2e2372 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osu.Game.Overlays.Wiki.Markdown; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneWikiMarkdownContainer : OsuTestScene + { + private WikiMarkdownContainer markdownContainer; + + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + new Box + { + Colour = overlayColour.Background5, + RelativeSizeAxes = Axes.Both, + }, + new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Child = markdownContainer = new WikiMarkdownContainer() + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + }; + }); + + [Test] + public void TestLink() + { + AddStep("set current path", () => markdownContainer.CurrentPath = "Article_styling_criteria/"); + + AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)"); + + AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)"); + + AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)"); + + AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)"); + } + } +} From 98af998978c7f128733cda2dc97db0694a64fd10 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 25 May 2021 09:56:26 +0700 Subject: [PATCH 026/429] add test markdown class --- .../Online/TestSceneWikiMarkdownContainer.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index fd4c2e2372..993525e7db 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.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. +using System; +using Markdig.Syntax.Inlines; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; @@ -13,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online { public class TestSceneWikiMarkdownContainer : OsuTestScene { - private WikiMarkdownContainer markdownContainer; + private TestMarkdownContainer markdownContainer; [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); @@ -32,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(20), - Child = markdownContainer = new WikiMarkdownContainer() + Child = markdownContainer = new TestMarkdownContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -54,5 +58,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)"); } + + private class TestMarkdownContainer : WikiMarkdownContainer + { + public LinkInline Link; + + public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer + { + UrlAdded = link => Link = link, + }; + + private class TestMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer + { + public Action UrlAdded; + + protected override void AddLinkText(string text, LinkInline linkInline) + { + base.AddLinkText(text, linkInline); + + UrlAdded?.Invoke(linkInline); + } + } + } } } From 5c1c43dea395211307b848bfa3dbdd218289814b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 25 May 2021 10:44:22 +0700 Subject: [PATCH 027/429] add link assert --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 993525e7db..cadf876094 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers.Markdown; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; @@ -22,6 +23,9 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); + [Cached] + private readonly IAPIProvider api = new DummyAPIAccess(); + [SetUp] public void Setup() => Schedule(() => { @@ -51,12 +55,16 @@ namespace osu.Game.Tests.Visual.Online AddStep("set current path", () => markdownContainer.CurrentPath = "Article_styling_criteria/"); AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/Main_Page"); AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/FAQ"); AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing"); AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting"); } private class TestMarkdownContainer : WikiMarkdownContainer From 292f314feeba625149aa30b639503ca794b2a67f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 16:04:08 +0700 Subject: [PATCH 028/429] initial wiki notice container --- .../Wiki/Markdown/WikiMarkdownContainer.cs | 19 +++++++++++++++++++ .../Wiki/Markdown/WikiNoticeContainer.cs | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index ac128d79dc..3dfb9828cf 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.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 Markdig.Extensions.Yaml; +using Markdig.Syntax; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers.Markdown; namespace osu.Game.Overlays.Wiki.Markdown @@ -11,5 +14,21 @@ namespace osu.Game.Overlays.Wiki.Markdown { set => Schedule(() => DocumentUrl += $"wiki/{value}"); } + + protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) + { + switch (markdownObject) + { + case YamlFrontMatterBlock yamlFrontMatterBlock: + container.Add(CreateNotice(yamlFrontMatterBlock)); + break; + + default: + base.AddMarkdownComponent(markdownObject, container, level); + break; + } + } + + protected virtual FillFlowContainer CreateNotice(YamlFrontMatterBlock yamlFrontMatterBlock) => new WikiNoticeContainer(yamlFrontMatterBlock); } } diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs new file mode 100644 index 0000000000..e42834cea7 --- /dev/null +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.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 Markdig.Extensions.Yaml; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Overlays.Wiki.Markdown +{ + public class WikiNoticeContainer : FillFlowContainer + { + public WikiNoticeContainer(YamlFrontMatterBlock yamlFrontMatterBlock) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + } + } +} From 8f1b48d766ad31253a3a105c832120516d488b58 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 17:29:48 +0700 Subject: [PATCH 029/429] add test for notice box --- .../Online/TestSceneWikiMarkdownContainer.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index cadf876094..018e1bf9f1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -67,6 +67,40 @@ namespace osu.Game.Tests.Visual.Online AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting"); } + [Test] + public void TestOutdatedNoticeBox() + { + AddStep("Add outdated yaml header", () => + { + markdownContainer.Text = @"--- +outdated: true +---"; + }); + } + + [Test] + public void TestNeedsCleanupNoticeBox() + { + AddStep("Add needs cleanup yaml header", () => + { + markdownContainer.Text = @"--- +needs_cleanup: true +---"; + }); + } + + [Test] + public void TestOnlyShowOutdatedNoticeBox() + { + AddStep("Add outdated and needs cleanup yaml", () => + { + markdownContainer.Text = @"--- +outdated: true +needs_cleanup: true +---"; + }); + } + private class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From b3eff65a0cf70742e9dca2dae768015b988b06e5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 16:05:37 +0700 Subject: [PATCH 030/429] parse isOutdated and needsCleanup --- .../Wiki/Markdown/WikiNoticeContainer.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index e42834cea7..3070d20798 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -9,11 +9,28 @@ namespace osu.Game.Overlays.Wiki.Markdown { public class WikiNoticeContainer : FillFlowContainer { + private readonly bool isOutdated; + private readonly bool needsCleanup; + public WikiNoticeContainer(YamlFrontMatterBlock yamlFrontMatterBlock) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; + + foreach (var line in yamlFrontMatterBlock.Lines) + { + switch (line.ToString()) + { + case "outdated: true": + isOutdated = true; + break; + + case "needs_cleanup: true": + needsCleanup = true; + break; + } + } } } } From e23ea00197ff7eb0c6d2e719aa7f54b3c5625025 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 16:06:43 +0700 Subject: [PATCH 031/429] add notice box --- .../Wiki/Markdown/WikiNoticeContainer.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index 3070d20798..f2821d14eb 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -2,8 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using Markdig.Extensions.Yaml; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; namespace osu.Game.Overlays.Wiki.Markdown { @@ -32,5 +36,41 @@ namespace osu.Game.Overlays.Wiki.Markdown } } } + + private class NoticeBox : Container + { + [Resolved] + private IMarkdownTextFlowComponent parentFlowComponent { get; set; } + + public string Text { get; set; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colour) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + MarkdownTextFlowContainer textFlow; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + textFlow = parentFlowComponent.CreateTextFlow().With(t => + { + t.Colour = colour.Orange1; + t.Padding = new MarginPadding + { + Vertical = 10, + Horizontal = 15, + }; + }) + }; + + textFlow.AddText(Text); + } + } } } From 7a8a37f4a0e6536fdd659e447e820a37e746937a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 16:08:30 +0700 Subject: [PATCH 032/429] load notice box --- .../Wiki/Markdown/WikiNoticeContainer.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index f2821d14eb..421806eea8 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -37,6 +37,27 @@ namespace osu.Game.Overlays.Wiki.Markdown } } + [BackgroundDependencyLoader] + private void load() + { + // Reference : https://github.com/ppy/osu-web/blob/master/resources/views/wiki/_notice.blade.php and https://github.com/ppy/osu-web/blob/master/resources/lang/en/wiki.php + // TODO : add notice box for fallback translation, legal translation and outdated translation after implement wiki locale in the future. + if (isOutdated) + { + Add(new NoticeBox + { + Text = "The content on this page is incomplete or outdated. If you are able to help out, please consider updating the article!", + }); + } + else if (needsCleanup) + { + Add(new NoticeBox + { + Text = "This page does not meet the standards of the osu! wiki and needs to be cleaned up or rewritten. If you are able to help out, please consider updating the article!", + }); + } + } + private class NoticeBox : Container { [Resolved] From 6585dd3a3e7eea2d7db371111b92a63434cab84a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 16:30:41 +0700 Subject: [PATCH 033/429] add image test --- .../Online/TestSceneWikiMarkdownContainer.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 018e1bf9f1..3731e7a782 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -101,6 +101,25 @@ needs_cleanup: true }); } + [Test] + public void TestAbsoluteImage() + { + AddStep("Add absolute image", () => + { + markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)"; + }); + } + + [Test] + public void TestRelativeImage() + { + AddStep("Add relative image", () => + { + markdownContainer.CurrentPath = "Interface/"; + markdownContainer.Text = "![intro](img/intro-screen.jpg)"; + }); + } + private class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From ef707bd09951b88a39f75ce889cd0524f78c3e5a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 2 May 2021 22:02:06 +0700 Subject: [PATCH 034/429] add WikiMarkdownImage --- .../Wiki/Markdown/WikiMarkdownContainer.cs | 3 ++ .../Wiki/Markdown/WikiMarkdownImage.cs | 38 +++++++++++++++++++ .../Markdown/WikiMarkdownTextFlowContainer.cs | 13 +++++++ 3 files changed, 54 insertions(+) create mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs create mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 3dfb9828cf..994bab7d04 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -4,6 +4,7 @@ using Markdig.Extensions.Yaml; using Markdig.Syntax; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics.Containers.Markdown; namespace osu.Game.Overlays.Wiki.Markdown @@ -29,6 +30,8 @@ namespace osu.Game.Overlays.Wiki.Markdown } } + public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer(); + protected virtual FillFlowContainer CreateNotice(YamlFrontMatterBlock yamlFrontMatterBlock) => new WikiNoticeContainer(yamlFrontMatterBlock); } } diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs new file mode 100644 index 0000000000..361aa2e95f --- /dev/null +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.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 Markdig.Syntax.Inlines; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Cursor; +using osu.Game.Online.API; + +namespace osu.Game.Overlays.Wiki.Markdown +{ + public class WikiMarkdownImage : MarkdownImage, IHasTooltip + { + private readonly string url; + + public string TooltipText { get; } + + public WikiMarkdownImage(LinkInline linkInline) + : base(linkInline.Url) + { + url = linkInline.Url; + TooltipText = linkInline.Title; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + // The idea is replace "{api.WebsiteRootUrl}/wiki/{path-to-image}" to "{api.WebsiteRootUrl}/wiki/images/{path-to-image}" + // "/wiki/images/*" is route to fetch wiki image from osu!web server (see: https://github.com/ppy/osu-web/blob/4205eb66a4da86bdee7835045e4bf28c35456e04/routes/web.php#L289) + // Currently all image in dev server (https://dev.ppy.sh/wiki/image/*) is 404 + // So for now just replace "{api.WebsiteRootUrl}/wiki/*" to "https://osu.ppy.sh/wiki/images/*" for simplicity + var imageUrl = url.Replace($"{api.WebsiteRootUrl}/wiki", "https://osu.ppy.sh/wiki/images"); + + InternalChild = new DelayedLoadWrapper(CreateImageContainer(imageUrl)); + } + } +} diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs new file mode 100644 index 0000000000..1c2b37a219 --- /dev/null +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Syntax.Inlines; +using osu.Game.Graphics.Containers.Markdown; + +namespace osu.Game.Overlays.Wiki.Markdown +{ + public class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer + { + protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline)); + } +} From b78ec8307df7880f518626bf24160ece2d6dbd12 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 2 May 2021 22:22:22 +0700 Subject: [PATCH 035/429] add markdown paragraph with image block --- .../Wiki/Markdown/WikiMarkdownContainer.cs | 2 + .../Wiki/Markdown/WikiMarkdownParagraph.cs | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 994bab7d04..81115293d4 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Overlays.Wiki.Markdown public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer(); + protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) => new WikiMarkdownParagraph(paragraphBlock); + protected virtual FillFlowContainer CreateNotice(YamlFrontMatterBlock yamlFrontMatterBlock) => new WikiNoticeContainer(yamlFrontMatterBlock); } } diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs new file mode 100644 index 0000000000..4a7ce24aba --- /dev/null +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Markdig.Syntax; +using Markdig.Syntax.Inlines; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osuTK; + +namespace osu.Game.Overlays.Wiki.Markdown +{ + public class WikiMarkdownParagraph : MarkdownParagraph + { + private readonly ParagraphBlock paragraphBlock; + + public WikiMarkdownParagraph(ParagraphBlock paragraphBlock) + : base(paragraphBlock) + { + this.paragraphBlock = paragraphBlock; + } + + [BackgroundDependencyLoader] + private void load() + { + MarkdownTextFlowContainer textFlow; + InternalChild = textFlow = CreateTextFlow(); + textFlow.AddInlineText(paragraphBlock.Inline); + + // Check if paragraph only contains an image. + if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline) + { + textFlow.TextAnchor = Anchor.TopCentre; + textFlow.Spacing = new Vector2(0, 5); + textFlow.AddText($"\n{linkInline.Title}"); + } + } + } +} 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 036/429] 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 037/429] 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 038/429] 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 039/429] 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 040/429] 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 041/429] 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 042/429] 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 043/429] 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 044/429] 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 045/429] 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 046/429] 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 075350e12572188f25d5a8b02fedf1a9edf73f83 Mon Sep 17 00:00:00 2001 From: Ibby Date: Sun, 9 May 2021 15:51:17 +1000 Subject: [PATCH 047/429] Adding a reset button to individual keybinds --- .../Settings/TestSceneKeyBindingPanel.cs | 40 +++++++++++++++++++ osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 28 +++++++++++++ 2 files changed, 68 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index f495e0fb23..75bbd8f86c 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -104,6 +104,46 @@ namespace osu.Game.Tests.Visual.Settings } } + [Test] + public void TestSingleBindResetButton() + { + KeyBindingRow multiBindingRow = null; + + AddStep("click first row with two bindings", () => + { + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + InputManager.MoveMouseTo(multiBindingRow); + InputManager.Click(MouseButton.Left); + }); + + clickSingleBindResetButton(); + + AddAssert("first binding cleared", () => multiBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(0))); + + AddStep("click second binding", () => + { + var target = multiBindingRow.ChildrenOfType().ElementAt(1); + + InputManager.MoveMouseTo(target); + InputManager.Click(MouseButton.Left); + }); + + clickSingleBindResetButton(); + + AddAssert("second binding cleared", () => multiBindingRow.ChildrenOfType().ElementAt(1).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(1))); + + void clickSingleBindResetButton() + { + AddStep("click reset button for single binding", () => + { + var clearButton = multiBindingRow.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(clearButton); + InputManager.Click(MouseButton.Left); + }); + } + } + [Test] public void TestClickRowSelectsFirstBinding() { diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 300fce962a..1fbaa374d4 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -109,6 +109,7 @@ namespace osu.Game.Overlays.KeyBinding Children = new Drawable[] { new CancelButton { Action = finalise }, + new SingleBindResetButton { Action = singleBindReset }, new ClearButton { Action = clear }, }, } @@ -281,6 +282,15 @@ namespace osu.Game.Overlays.KeyBinding finalise(); } + private void singleBindReset() + { + if (bindTarget == null) + return; + + bindTarget.UpdateKeyCombination(Defaults.ElementAt(buttons.IndexOf(bindTarget))); + finalise(); + } + private void finalise() { if (bindTarget != null) @@ -339,6 +349,24 @@ namespace osu.Game.Overlays.KeyBinding } } + public class SingleBindResetButton : TriangleButton + { + public SingleBindResetButton() + { + Text = "Reset"; + Size = new Vector2(80, 20); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Green; + + Triangles.ColourDark = colours.GreenDark; + Triangles.ColourLight = colours.GreenLight; + } + } + public class ClearButton : TriangleButton { public ClearButton() From 1a465c60ca54ea5f386bab902b62df12f512ac34 Mon Sep 17 00:00:00 2001 From: Swords Date: Sun, 9 May 2021 16:07:18 +1000 Subject: [PATCH 048/429] Rename Tests --- osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 75bbd8f86c..41b65e84b6 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestSingleBindResetButton() + public void TestResetButtonOnBindings() { KeyBindingRow multiBindingRow = null; From 7ca3e13712b974ce4cee0b431602ddf2fa654eed Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 07:43:01 +0300 Subject: [PATCH 049/429] 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 050/429] 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 051/429] 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 052/429] 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 053/429] 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 054/429] 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 055/429] 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 056/429] 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 057/429] 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 058/429] 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 059/429] 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 060/429] 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 061/429] 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 062/429] 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 063/429] 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 064/429] 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 065/429] 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 066/429] 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 067/429] 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 068/429] 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 069/429] 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 070/429] 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 071/429] 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 072/429] 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 073/429] 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 074/429] 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 075/429] 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 076/429] 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 1ae57a6105b124bbfb39a8a2f9e980014385f6f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 20:11:04 +0900 Subject: [PATCH 077/429] 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/429] 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/429] 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/429] 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 362a09ca73d491890698e1f84c6de2239ee6b129 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 21:41:18 +0900 Subject: [PATCH 081/429] 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 082/429] 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 0725088fdef8cd2aef018e78c8f29f74153a860f Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 01:01:17 +1000 Subject: [PATCH 083/429] Well it works, just pretty ugly looking. --- .../Settings/TestSceneKeyBindingPanel.cs | 69 +++++++--- .../Visual/Settings/TestSceneSettingsItem.cs | 5 +- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 76 ++++++----- .../KeyBinding/KeyBindingsSubsection.cs | 13 +- .../KeyBinding/SettingsKeyBindingRow.cs | 70 +++++++++++ .../Overlays/RestoreDefaultValueButton.cs | 119 ++++++++++++++++++ osu.Game/Overlays/Settings/SettingsItem.cs | 103 +-------------- 7 files changed, 299 insertions(+), 156 deletions(-) create mode 100644 osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs create mode 100644 osu.Game/Overlays/RestoreDefaultValueButton.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 41b65e84b6..09c8696ee0 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; +using osu.Framework.Input.Bindings; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Overlays; @@ -31,13 +32,16 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClickTwiceOnClearButton() { + SettingsKeyBindingRow firstSettingRow = null; KeyBindingRow firstRow = null; AddStep("click first row", () => { - firstRow = panel.ChildrenOfType().First(); - InputManager.MoveMouseTo(firstRow); + firstSettingRow = panel.ChildrenOfType().First(); + InputManager.MoveMouseTo(firstSettingRow); InputManager.Click(MouseButton.Left); + + firstRow = firstSettingRow.KeyBindingRow; }); AddStep("schedule button clicks", () => @@ -71,7 +75,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click first row with two bindings", () => { - multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + multiBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).KeyBindingRow; InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); @@ -105,38 +109,67 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestResetButtonOnBindings() + public void TestSingleBindResetButton() { + SettingsKeyBindingRow multiSettingsBindingRow = null; KeyBindingRow multiBindingRow = null; AddStep("click first row with two bindings", () => { + multiSettingsBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1); multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); + InputManager.PressKey(Key.P); + InputManager.ReleaseKey(Key.P); }); + AddUntilStep("restore button shown", () => multiSettingsBindingRow.ChildrenOfType>().First().Alpha > 0); + clickSingleBindResetButton(); AddAssert("first binding cleared", () => multiBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(0))); - - AddStep("click second binding", () => - { - var target = multiBindingRow.ChildrenOfType().ElementAt(1); - - InputManager.MoveMouseTo(target); - InputManager.Click(MouseButton.Left); - }); - - clickSingleBindResetButton(); - AddAssert("second binding cleared", () => multiBindingRow.ChildrenOfType().ElementAt(1).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(1))); + AddUntilStep("restore button hidden", () => multiSettingsBindingRow.ChildrenOfType>().First().Alpha == 0); + void clickSingleBindResetButton() { - AddStep("click reset button for single binding", () => + AddStep("click reset button for bindings", () => { - var clearButton = multiBindingRow.ChildrenOfType().Single(); + var clearButton = multiSettingsBindingRow.ChildrenOfType>().Single(); + + InputManager.MoveMouseTo(clearButton); + InputManager.Click(MouseButton.Left); + }); + } + } + + [Test] + public void TestResetAllBindingsButton() + { + SettingsKeyBindingRow multiSettingsBindingRow = null; + KeyBindingRow multiBindingRow = null; + + AddStep("click first row and press p", () => + { + multiSettingsBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1); + multiBindingRow = panel.ChildrenOfType().First(); + InputManager.MoveMouseTo(multiBindingRow); + InputManager.Click(MouseButton.Left); + InputManager.PressKey(Key.P); + InputManager.ReleaseKey(Key.P); + }); + + clickResetAllBindingsButton(); + + AddAssert("bindings cleared", () => multiBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(0))); + + void clickResetAllBindingsButton() + { + AddStep("click reset button for all bindings", () => + { + var clearButton = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(clearButton); InputManager.Click(MouseButton.Left); @@ -176,4 +209,4 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); } } -} +} \ No newline at end of file diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index 8f1c17ed29..f63145f534 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Overlays.Settings; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Settings { @@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Settings private class TestSettingsTextBox : SettingsTextBox { - public new Drawable RestoreDefaultValueButton => this.ChildrenOfType().Single(); + public Drawable RestoreDefaultValueButton => this.ChildrenOfType>().Single(); } } -} +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 1fbaa374d4..4d2738f244 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -4,12 +4,14 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -22,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Overlays.KeyBinding { - public class KeyBindingRow : Container, IFilterable + public class KeyBindingRow : Container, IFilterable, IHasCurrentValue { private readonly object action; private readonly IEnumerable bindings; @@ -51,6 +53,13 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; + private BindableWithCurrent isKeysDefaultValue; + public Bindable Current + { + get => isKeysDefaultValue.Current; + set => isKeysDefaultValue.Current = value; + } + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString()); public KeyBindingRow(object action, IEnumerable bindings) @@ -58,6 +67,11 @@ namespace osu.Game.Overlays.KeyBinding this.action = action; this.bindings = bindings; + isKeysDefaultValue = new BindableWithCurrent() + { + Default = true + }; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -71,6 +85,17 @@ namespace osu.Game.Overlays.KeyBinding [BackgroundDependencyLoader] private void load(OsuColour colours) { + + isKeysDefaultValue.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); + isKeysDefaultValue.BindValueChanged(resetButtons => + { + if (resetButtons.NewValue != resetButtons.OldValue && resetButtons.NewValue && !bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults)) + { + RestoreDefaults(); + finalise(); + } + }); + EdgeEffect = new EdgeEffectParameters { Radius = 2, @@ -109,7 +134,6 @@ namespace osu.Game.Overlays.KeyBinding Children = new Drawable[] { new CancelButton { Action = finalise }, - new SingleBindResetButton { Action = singleBindReset }, new ClearButton { Action = clear }, }, } @@ -129,6 +153,8 @@ namespace osu.Game.Overlays.KeyBinding button.UpdateKeyCombination(d); store.Update(button.KeyBinding); } + + isKeysDefaultValue.Value = true; } protected override bool OnHover(HoverEvent e) @@ -282,21 +308,29 @@ namespace osu.Game.Overlays.KeyBinding finalise(); } - private void singleBindReset() - { - if (bindTarget == null) - return; - - bindTarget.UpdateKeyCombination(Defaults.ElementAt(buttons.IndexOf(bindTarget))); - finalise(); - } - private void finalise() { if (bindTarget != null) { store.Update(bindTarget.KeyBinding); + KeyCombination keyDefault = Defaults.ElementAt(buttons.IndexOf(bindTarget)); + if (isKeysDefaultValue.Value) + { + if (!keyDefault.Equals(bindTarget.KeyBinding.KeyCombination)) + { + isKeysDefaultValue.Value = false; + } + } + else + { + if (keyDefault.Equals(bindTarget.KeyBinding.KeyCombination) && + buttons.Select(b => b.KeyBinding.KeyCombination).SequenceEqual(Defaults)) + { + isKeysDefaultValue.Value = true; + } + } + bindTarget.IsBinding = false; Schedule(() => { @@ -349,24 +383,6 @@ namespace osu.Game.Overlays.KeyBinding } } - public class SingleBindResetButton : TriangleButton - { - public SingleBindResetButton() - { - Text = "Reset"; - Size = new Vector2(80, 20); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Green; - - Triangles.ColourDark = colours.GreenDark; - Triangles.ColourLight = colours.GreenLight; - } - } - public class ClearButton : TriangleButton { public ClearButton() @@ -487,4 +503,4 @@ namespace osu.Game.Overlays.KeyBinding } } } -} +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index d784b7aec9..d130a3f536 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -1,17 +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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osuTK; using osu.Game.Graphics; +using osu.Framework.Input.Bindings; namespace osu.Game.Overlays.KeyBinding { @@ -41,16 +44,12 @@ namespace osu.Game.Overlays.KeyBinding int intKey = (int)defaultGroup.Key; // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals(intKey))) - { - AllowMainMouseButtons = Ruleset != null, - Defaults = defaultGroup.Select(d => d.KeyCombination) - }); + Add(new SettingsKeyBindingRow(defaultGroup, bindings, Ruleset)); } Add(new ResetButton { - Action = () => Children.OfType().ForEach(k => k.RestoreDefaults()) + Action = () => Children.OfType().ForEach(k => k.KeyBindingRow.RestoreDefaults()) }); } } @@ -72,4 +71,4 @@ namespace osu.Game.Overlays.KeyBinding Triangles.ColourLight = colours.Pink; } } -} +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs new file mode 100644 index 0000000000..da8a628b51 --- /dev/null +++ b/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs @@ -0,0 +1,70 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.KeyBinding +{ + public class SettingsKeyBindingRow : Container, IFilterable + { + private readonly IGrouping defaultGroup; + private readonly IEnumerable bindings; + public readonly KeyBindingRow KeyBindingRow; + + private bool matchingFilter; + + public bool MatchingFilter + { + get => matchingFilter; + set + { + matchingFilter = value; + this.FadeTo(!matchingFilter ? 0 : 1); + } + } + + public bool FilteringActive { get; set; } + + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(defaultGroup.Key.ToString()); + + public SettingsKeyBindingRow( + IGrouping defaultGroup, + IEnumerable bindings, + RulesetInfo ruleset) + { + this.defaultGroup = defaultGroup; + this.bindings = bindings; + + KeyBindingRow = new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals((int)defaultGroup.Key))) + { + AllowMainMouseButtons = ruleset != null, + Defaults = defaultGroup.Select(d => d.KeyCombination) + }; + + + RestoreDefaultValueButton restoreDefaultButton; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; + + InternalChildren = new Drawable[] + { + restoreDefaultButton = new RestoreDefaultValueButton(), + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Child = KeyBindingRow + }, + }; + + restoreDefaultButton.Bindable = KeyBindingRow.Current; + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs new file mode 100644 index 0000000000..6d6616a893 --- /dev/null +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -0,0 +1,119 @@ +// 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 osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays +{ + public class RestoreDefaultValueButton : Container, IHasTooltip + { + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + + private Bindable bindable; + + public Bindable Bindable + { + get => bindable; + set + { + bindable = value; + bindable.ValueChanged += _ => UpdateState(); + bindable.DisabledChanged += _ => UpdateState(); + bindable.DefaultChanged += _ => UpdateState(); + UpdateState(); + } + } + + private Color4 buttonColour; + + private bool hovering; + + public RestoreDefaultValueButton() + { + RelativeSizeAxes = Axes.Y; + Width = SettingsPanel.CONTENT_MARGINS; + Alpha = 0f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + buttonColour = colour.Yellow; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + CornerRadius = 3, + Masking = true, + Colour = buttonColour, + EdgeEffect = new EdgeEffectParameters + { + Colour = buttonColour.Opacity(0.1f), + Type = EdgeEffectType.Glow, + Radius = 2, + }, + Size = new Vector2(0.33f, 0.8f), + Child = new Box { RelativeSizeAxes = Axes.Both }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateState(); + } + + public string TooltipText => "revert to default"; + + protected override bool OnClick(ClickEvent e) + { + if (bindable != null && !bindable.Disabled) + bindable.SetDefault(); + return true; + } + + protected override bool OnHover(HoverEvent e) + { + hovering = true; + UpdateState(); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hovering = false; + UpdateState(); + } + + public void SetButtonColour(Color4 buttonColour) + { + this.buttonColour = buttonColour; + UpdateState(); + } + + public void UpdateState() => Scheduler.AddOnce(updateState); + + private void updateState() + { + if (bindable == null) + return; + + this.FadeTo(bindable.IsDefault ? 0f : + hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); + this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 86a836d29b..7677931fde 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -108,7 +108,8 @@ namespace osu.Game.Overlays.Settings protected SettingsItem() { - RestoreDefaultValueButton restoreDefaultButton; + + RestoreDefaultValueButton restoreDefaultButton; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -116,7 +117,7 @@ namespace osu.Game.Overlays.Settings InternalChildren = new Drawable[] { - restoreDefaultButton = new RestoreDefaultValueButton(), + restoreDefaultButton = new RestoreDefaultValueButton(), FlowContent = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -146,101 +147,5 @@ namespace osu.Game.Overlays.Settings if (labelText != null) labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } - - protected internal class RestoreDefaultValueButton : Container, IHasTooltip - { - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - - private Bindable bindable; - - public Bindable Bindable - { - get => bindable; - set - { - bindable = value; - bindable.ValueChanged += _ => UpdateState(); - bindable.DisabledChanged += _ => UpdateState(); - bindable.DefaultChanged += _ => UpdateState(); - UpdateState(); - } - } - - private Color4 buttonColour; - - private bool hovering; - - public RestoreDefaultValueButton() - { - RelativeSizeAxes = Axes.Y; - Width = SettingsPanel.CONTENT_MARGINS; - Padding = new MarginPadding { Vertical = 1.5f }; - Alpha = 0f; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - buttonColour = colour.Yellow; - - Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - CornerRadius = 3, - Masking = true, - Colour = buttonColour, - EdgeEffect = new EdgeEffectParameters - { - Colour = buttonColour.Opacity(0.1f), - Type = EdgeEffectType.Glow, - Radius = 2, - }, - Width = 0.33f, - Child = new Box { RelativeSizeAxes = Axes.Both }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - UpdateState(); - } - - public string TooltipText => "revert to default"; - - protected override bool OnClick(ClickEvent e) - { - if (bindable != null && !bindable.Disabled) - bindable.SetDefault(); - return true; - } - - protected override bool OnHover(HoverEvent e) - { - hovering = true; - UpdateState(); - return false; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hovering = false; - UpdateState(); - } - - public void UpdateState() => Scheduler.AddOnce(updateState); - - private void updateState() - { - if (bindable == null) - return; - - this.FadeTo(bindable.IsDefault ? 0f : - hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); - this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); - } - } } -} +} \ No newline at end of file From 753bdf208385285eb8ce24213365ddb74299a290 Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 01:04:15 +1000 Subject: [PATCH 084/429] Fixed formatting --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 1 - osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs | 1 - osu.Game/Overlays/Settings/SettingsItem.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 4d2738f244..eeb9a5f7ec 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -85,7 +85,6 @@ namespace osu.Game.Overlays.KeyBinding [BackgroundDependencyLoader] private void load(OsuColour colours) { - isKeysDefaultValue.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); isKeysDefaultValue.BindValueChanged(resetButtons => { diff --git a/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs index da8a628b51..15392efc9a 100644 --- a/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs @@ -45,7 +45,6 @@ namespace osu.Game.Overlays.KeyBinding Defaults = defaultGroup.Select(d => d.KeyCombination) }; - RestoreDefaultValueButton restoreDefaultButton; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 7677931fde..57d1549910 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -108,7 +108,6 @@ namespace osu.Game.Overlays.Settings protected SettingsItem() { - RestoreDefaultValueButton restoreDefaultButton; RelativeSizeAxes = Axes.X; From 1603b92211a478846e8db583747fb66826034ceb Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 01:30:54 +1000 Subject: [PATCH 085/429] Reformatting --- .../Settings/TestSceneKeyBindingPanel.cs | 19 +++++++------------ osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 6 ++++-- .../KeyBinding/KeyBindingsSubsection.cs | 5 ----- osu.Game/Overlays/Settings/SettingsItem.cs | 5 ----- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 09c8696ee0..9126808c88 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; -using osu.Framework.Input.Bindings; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Overlays; @@ -32,16 +31,14 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClickTwiceOnClearButton() { - SettingsKeyBindingRow firstSettingRow = null; KeyBindingRow firstRow = null; AddStep("click first row", () => { - firstSettingRow = panel.ChildrenOfType().First(); - InputManager.MoveMouseTo(firstSettingRow); + InputManager.MoveMouseTo(panel.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); - firstRow = firstSettingRow.KeyBindingRow; + firstRow = panel.ChildrenOfType().First().KeyBindingRow; }); AddStep("schedule button clicks", () => @@ -111,12 +108,10 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestSingleBindResetButton() { - SettingsKeyBindingRow multiSettingsBindingRow = null; KeyBindingRow multiBindingRow = null; AddStep("click first row with two bindings", () => { - multiSettingsBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1); multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); @@ -124,20 +119,20 @@ namespace osu.Game.Tests.Visual.Settings InputManager.ReleaseKey(Key.P); }); - AddUntilStep("restore button shown", () => multiSettingsBindingRow.ChildrenOfType>().First().Alpha > 0); + AddUntilStep("restore button shown", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType>().First().Alpha > 0); clickSingleBindResetButton(); - AddAssert("first binding cleared", () => multiBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(0))); - AddAssert("second binding cleared", () => multiBindingRow.ChildrenOfType().ElementAt(1).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(1))); + AddAssert("first binding cleared", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(0))); + AddAssert("second binding cleared", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType().ElementAt(1).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(1))); - AddUntilStep("restore button hidden", () => multiSettingsBindingRow.ChildrenOfType>().First().Alpha == 0); + AddUntilStep("restore button hidden", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType>().First().Alpha == 0); void clickSingleBindResetButton() { AddStep("click reset button for bindings", () => { - var clearButton = multiSettingsBindingRow.ChildrenOfType>().Single(); + var clearButton = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType>().Single(); InputManager.MoveMouseTo(clearButton); InputManager.Click(MouseButton.Left); diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index eeb9a5f7ec..3d89028f96 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -53,7 +53,8 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - private BindableWithCurrent isKeysDefaultValue; + private readonly BindableWithCurrent isKeysDefaultValue; + public Bindable Current { get => isKeysDefaultValue.Current; @@ -67,7 +68,7 @@ namespace osu.Game.Overlays.KeyBinding this.action = action; this.bindings = bindings; - isKeysDefaultValue = new BindableWithCurrent() + isKeysDefaultValue = new BindableWithCurrent { Default = true }; @@ -314,6 +315,7 @@ namespace osu.Game.Overlays.KeyBinding store.Update(bindTarget.KeyBinding); KeyCombination keyDefault = Defaults.ElementAt(buttons.IndexOf(bindTarget)); + if (isKeysDefaultValue.Value) { if (!keyDefault.Equals(bindTarget.KeyBinding.KeyCombination)) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index d130a3f536..84630703bd 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -1,20 +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; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osuTK; using osu.Game.Graphics; -using osu.Framework.Input.Bindings; namespace osu.Game.Overlays.KeyBinding { @@ -41,8 +38,6 @@ namespace osu.Game.Overlays.KeyBinding foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { - int intKey = (int)defaultGroup.Key; - // one row per valid action. Add(new SettingsKeyBindingRow(defaultGroup, bindings, Ruleset)); } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 57d1549910..aa5cbf3d4e 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -5,16 +5,11 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; From 843da30f9d0f3ae60687eef1ddb1180178585f08 Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 01:52:16 +1000 Subject: [PATCH 086/429] Reformatting --- osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs | 4 +--- osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 9126808c88..cfef0ecf5a 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -143,13 +143,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestResetAllBindingsButton() { - SettingsKeyBindingRow multiSettingsBindingRow = null; KeyBindingRow multiBindingRow = null; AddStep("click first row and press p", () => { - multiSettingsBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1); - multiBindingRow = panel.ChildrenOfType().First(); + multiBindingRow = panel.ChildrenOfType().First().KeyBindingRow; InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); InputManager.PressKey(Key.P); diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 73f4e6c371..e07e5a52c6 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -11,7 +11,6 @@ using osu.Game.Input; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osuTK; -using osu.Game.Graphics; namespace osu.Game.Overlays.KeyBinding { From 304caf8bdf79aabbd588d07b907e85e32915ba8e Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 11:24:08 +1000 Subject: [PATCH 087/429] Adding Requested changed --- .../Settings/TestSceneKeyBindingPanel.cs | 67 ++++++++----------- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 32 ++------- .../KeyBinding/SettingsKeyBindingRow.cs | 6 +- .../Overlays/RestoreDefaultValueButton.cs | 33 ++++----- osu.Game/Overlays/Settings/SettingsItem.cs | 2 +- 5 files changed, 56 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index cfef0ecf5a..669338d714 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -35,10 +35,10 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click first row", () => { - InputManager.MoveMouseTo(panel.ChildrenOfType().First()); - InputManager.Click(MouseButton.Left); + firstRow = panel.ChildrenOfType().First(); - firstRow = panel.ChildrenOfType().First().KeyBindingRow; + InputManager.MoveMouseTo(firstRow); + InputManager.Click(MouseButton.Left); }); AddStep("schedule button clicks", () => @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click first row with two bindings", () => { - multiBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).KeyBindingRow; + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); @@ -108,66 +108,55 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestSingleBindResetButton() { - KeyBindingRow multiBindingRow = null; + SettingsKeyBindingRow settingsKeyBindingRow = null; AddStep("click first row with two bindings", () => { - multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); - InputManager.MoveMouseTo(multiBindingRow); + settingsKeyBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1); + InputManager.MoveMouseTo(settingsKeyBindingRow.KeyBindingRow); InputManager.Click(MouseButton.Left); InputManager.PressKey(Key.P); InputManager.ReleaseKey(Key.P); }); - AddUntilStep("restore button shown", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType>().First().Alpha > 0); + AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha > 0); - clickSingleBindResetButton(); - - AddAssert("first binding cleared", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(0))); - AddAssert("second binding cleared", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType().ElementAt(1).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(1))); - - AddUntilStep("restore button hidden", () => panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType>().First().Alpha == 0); - - void clickSingleBindResetButton() + AddStep("click reset button for bindings", () => { - AddStep("click reset button for bindings", () => - { - var clearButton = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1).ChildrenOfType>().Single(); + var clearButton = settingsKeyBindingRow.ChildrenOfType>().Single(); - InputManager.MoveMouseTo(clearButton); - InputManager.Click(MouseButton.Left); - }); - } + InputManager.MoveMouseTo(clearButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); + + AddAssert("first binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); + AddAssert("second binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(1).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(1))); } [Test] public void TestResetAllBindingsButton() { - KeyBindingRow multiBindingRow = null; + KeyBindingRow keyBindingRow = null; AddStep("click first row and press p", () => { - multiBindingRow = panel.ChildrenOfType().First().KeyBindingRow; - InputManager.MoveMouseTo(multiBindingRow); + keyBindingRow = panel.ChildrenOfType().First(); + InputManager.MoveMouseTo(keyBindingRow); InputManager.Click(MouseButton.Left); InputManager.PressKey(Key.P); InputManager.ReleaseKey(Key.P); }); - - clickResetAllBindingsButton(); - - AddAssert("bindings cleared", () => multiBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(multiBindingRow.Defaults.ElementAt(0))); - - void clickResetAllBindingsButton() + AddStep("click reset button for all bindings", () => { - AddStep("click reset button for all bindings", () => - { - var clearButton = panel.ChildrenOfType().First(); + var clearButton = panel.ChildrenOfType().First(); - InputManager.MoveMouseTo(clearButton); - InputManager.Click(MouseButton.Left); - }); - } + InputManager.MoveMouseTo(clearButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("bindings cleared", () => keyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(keyBindingRow.Defaults.ElementAt(0))); } [Test] diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 8336dae9ac..6f4c5f1179 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -53,7 +53,11 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - private readonly BindableWithCurrent isKeysDefaultValue; + private readonly BindableWithCurrent isKeysDefaultValue = new BindableWithCurrent + { + Default = true + }; + public Bindable Current { @@ -67,12 +71,6 @@ namespace osu.Game.Overlays.KeyBinding { this.action = action; this.bindings = bindings; - - isKeysDefaultValue = new BindableWithCurrent - { - Default = true - }; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -89,7 +87,7 @@ namespace osu.Game.Overlays.KeyBinding isKeysDefaultValue.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); isKeysDefaultValue.BindValueChanged(resetButtons => { - if (resetButtons.NewValue != resetButtons.OldValue && resetButtons.NewValue && !bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults)) + if (resetButtons.NewValue && !bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults)) { RestoreDefaults(); finalise(); @@ -314,23 +312,7 @@ namespace osu.Game.Overlays.KeyBinding { store.Update(bindTarget.KeyBinding); - KeyCombination keyDefault = Defaults.ElementAt(buttons.IndexOf(bindTarget)); - - if (isKeysDefaultValue.Value) - { - if (!keyDefault.Equals(bindTarget.KeyBinding.KeyCombination)) - { - isKeysDefaultValue.Value = false; - } - } - else - { - if (keyDefault.Equals(bindTarget.KeyBinding.KeyCombination) && - buttons.Select(b => b.KeyBinding.KeyCombination).SequenceEqual(Defaults)) - { - isKeysDefaultValue.Value = true; - } - } + isKeysDefaultValue.Value = buttons.Select(b => b.KeyBinding.KeyCombination).SequenceEqual(Defaults); bindTarget.IsBinding = false; Schedule(() => diff --git a/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs index 15392efc9a..31374a8120 100644 --- a/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.KeyBinding public class SettingsKeyBindingRow : Container, IFilterable { private readonly IGrouping defaultGroup; - private readonly IEnumerable bindings; + private readonly ICollection bindings; public readonly KeyBindingRow KeyBindingRow; private bool matchingFilter; @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.KeyBinding public SettingsKeyBindingRow( IGrouping defaultGroup, - IEnumerable bindings, + ICollection bindings, RulesetInfo ruleset) { this.defaultGroup = defaultGroup; @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.KeyBinding }, }; - restoreDefaultButton.Bindable = KeyBindingRow.Current; + restoreDefaultButton.Current = KeyBindingRow.Current; } } } \ No newline at end of file diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 6d6616a893..5d61b76915 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -10,27 +10,27 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; namespace osu.Game.Overlays { - public class RestoreDefaultValueButton : Container, IHasTooltip + public class RestoreDefaultValueButton : Container, IHasTooltip, IHasCurrentValue { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private Bindable bindable; + private BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Bindable + public Bindable Current { - get => bindable; - set - { - bindable = value; - bindable.ValueChanged += _ => UpdateState(); - bindable.DisabledChanged += _ => UpdateState(); - bindable.DefaultChanged += _ => UpdateState(); + get => current.Current; + set { + current.Current = value; + current.ValueChanged += _ => UpdateState(); + current.DisabledChanged += _ => UpdateState(); + current.DefaultChanged += _ => UpdateState(); UpdateState(); } } @@ -43,6 +43,7 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Y; Width = SettingsPanel.CONTENT_MARGINS; + Padding = new MarginPadding { Vertical = 1.5f }; Alpha = 0f; } @@ -80,8 +81,8 @@ namespace osu.Game.Overlays protected override bool OnClick(ClickEvent e) { - if (bindable != null && !bindable.Disabled) - bindable.SetDefault(); + if (current != null && !current.Disabled) + current.SetDefault(); return true; } @@ -108,12 +109,12 @@ namespace osu.Game.Overlays private void updateState() { - if (bindable == null) + if (current == null) return; - this.FadeTo(bindable.IsDefault ? 0f : - hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); - this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); + this.FadeTo(current.IsDefault ? 0f : + hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); + this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } } \ No newline at end of file diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index aa5cbf3d4e..807916e7f6 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.Settings controlWithCurrent.Current.DisabledChanged += _ => updateDisabled(); if (ShowsDefaultIndicator) - restoreDefaultButton.Bindable = controlWithCurrent.Current; + restoreDefaultButton.Current = controlWithCurrent.Current; } } From d85f17159f0e3f5e3cf4b61165ec1f9de5339837 Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 11:25:56 +1000 Subject: [PATCH 088/429] Formatting --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 6f4c5f1179..7e81f64458 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -58,7 +58,6 @@ namespace osu.Game.Overlays.KeyBinding Default = true }; - public Bindable Current { get => isKeysDefaultValue.Current; From d988194dd3638749bcf3e53ef6f08ca63928ca52 Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 11:40:42 +1000 Subject: [PATCH 089/429] Formatting --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 5d61b76915..e709e874ce 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -21,12 +21,13 @@ namespace osu.Game.Overlays { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); public Bindable Current { get => current.Current; - set { + set + { current.Current = value; current.ValueChanged += _ => UpdateState(); current.DisabledChanged += _ => UpdateState(); From 50d5af9662475aba7dadbe7302750ca57f3d9899 Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 12:05:22 +1000 Subject: [PATCH 090/429] Removed unused method --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index e709e874ce..5c07eac838 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -100,12 +100,6 @@ namespace osu.Game.Overlays UpdateState(); } - public void SetButtonColour(Color4 buttonColour) - { - this.buttonColour = buttonColour; - UpdateState(); - } - public void UpdateState() => Scheduler.AddOnce(updateState); private void updateState() From c282f0e6035a2388d0360086bceed1d96d04d931 Mon Sep 17 00:00:00 2001 From: Swords Date: Sat, 15 May 2021 19:42:33 +1000 Subject: [PATCH 091/429] Fixing tests For some reason moving the mouse and clicking doesn't work with "dotnet test", but works when you run the osu.Game.Tests project. --- .../Settings/TestSceneKeyBindingPanel.cs | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 669338d714..8053a41de2 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -106,14 +106,15 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestSingleBindResetButton() + public void TestSingleBindingResetButton() { SettingsKeyBindingRow settingsKeyBindingRow = null; - AddStep("click first row with two bindings", () => + AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(row => row.KeyBindingRow.Defaults.Count() > 1); - InputManager.MoveMouseTo(settingsKeyBindingRow.KeyBindingRow); + settingsKeyBindingRow = panel.ChildrenOfType().First(); + + InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); InputManager.PressKey(Key.P); InputManager.ReleaseKey(Key.P); @@ -123,40 +124,43 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click reset button for bindings", () => { - var clearButton = settingsKeyBindingRow.ChildrenOfType>().Single(); + var resetButton = settingsKeyBindingRow.ChildrenOfType>().First(); - InputManager.MoveMouseTo(clearButton); - InputManager.Click(MouseButton.Left); + resetButton.Click(); }); AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("first binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); - AddAssert("second binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(1).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(1))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); } [Test] public void TestResetAllBindingsButton() { - KeyBindingRow keyBindingRow = null; + SettingsKeyBindingRow settingsKeyBindingRow = null; - AddStep("click first row and press p", () => + AddStep("click first row", () => { - keyBindingRow = panel.ChildrenOfType().First(); - InputManager.MoveMouseTo(keyBindingRow); + settingsKeyBindingRow = panel.ChildrenOfType().First(); + + InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); InputManager.PressKey(Key.P); InputManager.ReleaseKey(Key.P); }); - AddStep("click reset button for all bindings", () => - { - var clearButton = panel.ChildrenOfType().First(); - InputManager.MoveMouseTo(clearButton); - InputManager.Click(MouseButton.Left); + AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha > 0); + + AddStep("click reset button for bindings", () => + { + var resetButton = panel.ChildrenOfType().First(); + + resetButton.Click(); }); - AddUntilStep("bindings cleared", () => keyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(keyBindingRow.Defaults.ElementAt(0))); + AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); + + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); } [Test] From e479db91864dd9a097aa90407940fd9b3639004b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 15 May 2021 19:14:02 +0300 Subject: [PATCH 092/429] 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 093/429] 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 264d8b9b86c17f09448c78df43aa6d904c492201 Mon Sep 17 00:00:00 2001 From: Swords Date: Sun, 16 May 2021 14:48:00 +1000 Subject: [PATCH 094/429] Finishing requested changes, and tidy up --- .../Settings/TestSceneKeyBindingPanel.cs | 8 +++--- .../KeyBinding/KeyBindingsSubsection.cs | 4 +-- ...ndingRow.cs => RestorableKeyBindingRow.cs} | 28 ++++++++++--------- 3 files changed, 21 insertions(+), 19 deletions(-) rename osu.Game/Overlays/KeyBinding/{SettingsKeyBindingRow.cs => RestorableKeyBindingRow.cs} (74%) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 8053a41de2..3edba2ddd7 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -108,11 +108,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestSingleBindingResetButton() { - SettingsKeyBindingRow settingsKeyBindingRow = null; + RestorableKeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); @@ -137,11 +137,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestResetAllBindingsButton() { - SettingsKeyBindingRow settingsKeyBindingRow = null; + RestorableKeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index e07e5a52c6..dc29f0b4e5 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -38,12 +38,12 @@ namespace osu.Game.Overlays.KeyBinding foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { // one row per valid action. - Add(new SettingsKeyBindingRow(defaultGroup, bindings, Ruleset)); + Add(new RestorableKeyBindingRow(defaultGroup.Key, bindings, Ruleset, defaultGroup.Select(d => d.KeyCombination))); } Add(new ResetButton { - Action = () => Children.OfType().ForEach(k => k.KeyBindingRow.RestoreDefaults()) + Action = () => Children.OfType().ForEach(k => k.KeyBindingRow.RestoreDefaults()) }); } } diff --git a/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs similarity index 74% rename from osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs rename to osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index 31374a8120..0461ae4f35 100644 --- a/osu.Game/Overlays/KeyBinding/SettingsKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -5,13 +5,14 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; using osu.Game.Rulesets; namespace osu.Game.Overlays.KeyBinding { - public class SettingsKeyBindingRow : Container, IFilterable + public class RestorableKeyBindingRow : Container, IFilterable { - private readonly IGrouping defaultGroup; + private readonly object key; private readonly ICollection bindings; public readonly KeyBindingRow KeyBindingRow; @@ -29,28 +30,29 @@ namespace osu.Game.Overlays.KeyBinding public bool FilteringActive { get; set; } - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(defaultGroup.Key.ToString()); + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - public SettingsKeyBindingRow( - IGrouping defaultGroup, + public RestorableKeyBindingRow( + object key, ICollection bindings, - RulesetInfo ruleset) + RulesetInfo ruleset, + IEnumerable defaults) { - this.defaultGroup = defaultGroup; + this.key = key; this.bindings = bindings; - KeyBindingRow = new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals((int)defaultGroup.Key))) - { - AllowMainMouseButtons = ruleset != null, - Defaults = defaultGroup.Select(d => d.KeyCombination) - }; - RestoreDefaultValueButton restoreDefaultButton; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; + KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) + { + AllowMainMouseButtons = ruleset != null, + Defaults = defaults + }; + InternalChildren = new Drawable[] { restoreDefaultButton = new RestoreDefaultValueButton(), From 90f00a76633eb622b72073e32214ff4fc62710e3 Mon Sep 17 00:00:00 2001 From: Swords Date: Sun, 16 May 2021 15:01:19 +1000 Subject: [PATCH 095/429] Fixes ResotreDefaultValue to use the BindableWithCurrent correctly --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 5c07eac838..f390fb1e46 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -29,9 +29,6 @@ namespace osu.Game.Overlays set { current.Current = value; - current.ValueChanged += _ => UpdateState(); - current.DisabledChanged += _ => UpdateState(); - current.DefaultChanged += _ => UpdateState(); UpdateState(); } } @@ -46,6 +43,10 @@ namespace osu.Game.Overlays Width = SettingsPanel.CONTENT_MARGINS; Padding = new MarginPadding { Vertical = 1.5f }; Alpha = 0f; + + Current.ValueChanged += _ => UpdateState(); + Current.DisabledChanged += _ => UpdateState(); + Current.DefaultChanged += _ => UpdateState(); } [BackgroundDependencyLoader] From 50e2b5a32761768102975a76511d099bc51180d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:00:36 +0900 Subject: [PATCH 096/429] 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 097/429] 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 098/429] 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 099/429] 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 100/429] 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 e754d2e59028940ff1a2e8b06cf66e88d1187299 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 10:54:45 +0300 Subject: [PATCH 101/429] 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 5059bfaef99e987293d6b3f3d3f5b7629dcab9d9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:17:02 +0300 Subject: [PATCH 102/429] 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 103/429] 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 104/429] 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 105/429] 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 106/429] 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 107/429] 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 7137315fa7612f02bbd963e55e6b2c6ac512b892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 19:46:50 +0900 Subject: [PATCH 108/429] 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 109/429] 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 110/429] 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 111/429] 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 112/429] 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 113/429] 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 114/429] 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 115/429] 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 116/429] 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 117/429] 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 118/429] 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 119/429] 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 120/429] 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 121/429] 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 122/429] 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 123/429] 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 124/429] 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 125/429] 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 126/429] 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 127/429] 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 128/429] 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 129/429] 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 130/429] 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 33fe843ba9eba9fcdedd8a59969b016a2abde848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 20:44:47 +0200 Subject: [PATCH 131/429] Move value change bindings to `LoadComplete()` Also removes a redundant one from the setter of `Current`. If the set to current comes with an associated value change, the bind-unbind flow that `BindableWithCurrent` implements will handle that change anyway. --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index f390fb1e46..34ebe270ef 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -26,11 +26,7 @@ namespace osu.Game.Overlays public Bindable Current { get => current.Current; - set - { - current.Current = value; - UpdateState(); - } + set => current.Current = value; } private Color4 buttonColour; @@ -43,10 +39,6 @@ namespace osu.Game.Overlays Width = SettingsPanel.CONTENT_MARGINS; Padding = new MarginPadding { Vertical = 1.5f }; Alpha = 0f; - - Current.ValueChanged += _ => UpdateState(); - Current.DisabledChanged += _ => UpdateState(); - Current.DefaultChanged += _ => UpdateState(); } [BackgroundDependencyLoader] @@ -76,6 +68,11 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); + + Current.ValueChanged += _ => UpdateState(); + Current.DisabledChanged += _ => UpdateState(); + Current.DefaultChanged += _ => UpdateState(); + UpdateState(); } @@ -113,4 +110,4 @@ namespace osu.Game.Overlays this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } -} \ No newline at end of file +} From 30d7768971208bf50324aeb32bf103e1851cba30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 20:47:56 +0200 Subject: [PATCH 132/429] Remove now-redundant null check --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 34ebe270ef..3f4a9785a6 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays protected override bool OnClick(ClickEvent e) { - if (current != null && !current.Disabled) + if (!current.Disabled) current.SetDefault(); return true; } From 8530b31e39ca3eb5d6e275cc5f88ae9d5baf8efa Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 21:02:45 +0200 Subject: [PATCH 133/429] 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 ef114f240799be93ef7448e7bf33da9693390dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 20:57:48 +0200 Subject: [PATCH 134/429] Simplify bindable flow in `KeyBindingRow` --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 45 ++++++++++--------- .../KeyBinding/RestorableKeyBindingRow.cs | 4 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 7e81f64458..33f32493ad 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -24,7 +23,7 @@ using osuTK.Input; namespace osu.Game.Overlays.KeyBinding { - public class KeyBindingRow : Container, IFilterable, IHasCurrentValue + public class KeyBindingRow : Container, IFilterable { private readonly object action; private readonly IEnumerable bindings; @@ -53,17 +52,11 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - private readonly BindableWithCurrent isKeysDefaultValue = new BindableWithCurrent + public Bindable IsDefault { get; } = new BindableBool(true) { Default = true }; - public Bindable Current - { - get => isKeysDefaultValue.Current; - set => isKeysDefaultValue.Current = value; - } - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString()); public KeyBindingRow(object action, IEnumerable bindings) @@ -83,15 +76,7 @@ namespace osu.Game.Overlays.KeyBinding [BackgroundDependencyLoader] private void load(OsuColour colours) { - isKeysDefaultValue.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); - isKeysDefaultValue.BindValueChanged(resetButtons => - { - if (resetButtons.NewValue && !bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults)) - { - RestoreDefaults(); - finalise(); - } - }); + updateIsDefaultValue(); EdgeEffect = new EdgeEffectParameters { @@ -140,6 +125,24 @@ namespace osu.Game.Overlays.KeyBinding buttons.Add(new KeyButton(b)); } + protected override void LoadComplete() + { + base.LoadComplete(); + + IsDefault.BindValueChanged(resetButtons => + { + if (resetButtons.NewValue && !computeIsDefaultValue()) + { + RestoreDefaults(); + finalise(); + } + }); + } + + private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); + + private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); + public void RestoreDefaults() { int i = 0; @@ -151,7 +154,7 @@ namespace osu.Game.Overlays.KeyBinding store.Update(button.KeyBinding); } - isKeysDefaultValue.Value = true; + updateIsDefaultValue(); } protected override bool OnHover(HoverEvent e) @@ -311,7 +314,7 @@ namespace osu.Game.Overlays.KeyBinding { store.Update(bindTarget.KeyBinding); - isKeysDefaultValue.Value = buttons.Select(b => b.KeyBinding.KeyCombination).SequenceEqual(Defaults); + updateIsDefaultValue(); bindTarget.IsBinding = false; Schedule(() => @@ -476,4 +479,4 @@ namespace osu.Game.Overlays.KeyBinding } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index 0461ae4f35..b09c21378e 100644 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays.KeyBinding }, }; - restoreDefaultButton.Current = KeyBindingRow.Current; + restoreDefaultButton.Current = KeyBindingRow.IsDefault; } } -} \ No newline at end of file +} From 0e91a00a7e3b4084ee3395c1d88d1437740a87f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 21:18:45 +0200 Subject: [PATCH 135/429] Revert to previous width spec instead of size --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 3f4a9785a6..51fb87da1d 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osuTK; namespace osu.Game.Overlays { @@ -60,7 +59,7 @@ namespace osu.Game.Overlays Type = EdgeEffectType.Glow, Radius = 2, }, - Size = new Vector2(0.33f, 0.8f), + Width = 0.33f, Child = new Box { RelativeSizeAxes = Axes.Both }, }; } From 79740dd2d8f70645becc7edf5134b3ee3a7041f2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 17 May 2021 22:01:05 +0200 Subject: [PATCH 136/429] 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 06389c08dc19287fdece6019170de4257365e7d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 13:11:22 +0900 Subject: [PATCH 137/429] 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 c71d53a0f9ce174dc65640879df256a55397b1c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 13:40:26 +0900 Subject: [PATCH 138/429] 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 3a5b21c0f5caafc98e93783f5d014dbc34841f8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 13:47:39 +0900 Subject: [PATCH 139/429] Update "reset all bindings" button to better match new key binding row widths --- osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index dc29f0b4e5..737c640b5a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -55,10 +55,13 @@ namespace osu.Game.Overlays.KeyBinding { Text = "Reset all bindings in section"; RelativeSizeAxes = Axes.X; - Margin = new MarginPadding { Top = 5 }; - Height = 20; + Width = 0.5f; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + Margin = new MarginPadding { Top = 15 }; + Height = 30; Content.CornerRadius = 5; } } -} \ No newline at end of file +} From 0100b88a8601a693de45531576d4b2ad32900ae0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 14:11:26 +0900 Subject: [PATCH 140/429] Move private methods down --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 33f32493ad..959ba36c6a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -129,9 +129,9 @@ namespace osu.Game.Overlays.KeyBinding { base.LoadComplete(); - IsDefault.BindValueChanged(resetButtons => + IsDefault.BindValueChanged(isDefault => { - if (resetButtons.NewValue && !computeIsDefaultValue()) + if (isDefault.NewValue && !computeIsDefaultValue()) { RestoreDefaults(); finalise(); @@ -139,10 +139,6 @@ namespace osu.Game.Overlays.KeyBinding }); } - private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); - - private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); - public void RestoreDefaults() { int i = 0; @@ -331,6 +327,10 @@ namespace osu.Game.Overlays.KeyBinding cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; } + private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); + + private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); + protected override void OnFocus(FocusEvent e) { AutoSizeDuration = 500; From 829d326e36cdab8a4a1be3e9624a0003d4910d48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 14:50:10 +0900 Subject: [PATCH 141/429] 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 142/429] 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 143/429] 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 144/429] 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 145/429] 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 146/429] 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 147/429] 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 148/429] 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 149/429] 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 150/429] 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 151/429] 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 152/429] 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 153/429] 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 154/429] 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 155/429] 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 156/429] 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 157/429] 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 158/429] 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 159/429] 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 160/429] 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 161/429] 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 162/429] 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 163/429] 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 164/429] 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 165/429] 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 166/429] 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 167/429] 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 168/429] 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 169/429] 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 170/429] 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 171/429] 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 124ac689b88e5ea7d48f5018c62454235f1eeceb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 16:26:27 +0900 Subject: [PATCH 172/429] Add method to `PopupDialog` to press the first OK button --- osu.Game/Overlays/Dialog/PopupDialog.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 1bcbe4dd2f..df085b56f2 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -207,6 +207,11 @@ namespace osu.Game.Overlays.Dialog }; } + /// + /// Programmatically clicks the first . + /// + public void PerformOkAction() => Buttons.OfType().First().Click(); + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Repeat) return false; From b83322281e2effed91b67d81aa10fd71e54f32df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 16:27:38 +0900 Subject: [PATCH 173/429] Consume updated logic at `MainMenu` --- osu.Game/Screens/Menu/MainMenu.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index baeb86c976..e53b46f391 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,9 +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 osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,6 +19,8 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Select; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Menu { @@ -120,7 +119,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.TopRight, Margin = new MarginPadding { Right = 15, Top = 5 } }, - exitConfirmOverlay?.CreateProxy() ?? Drawable.Empty() + exitConfirmOverlay?.CreateProxy() ?? Empty() }); buttons.StateChanged += state => @@ -270,15 +269,11 @@ namespace osu.Game.Screens.Menu if (!exitConfirmed && dialogOverlay != null) { if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog) - { - exitConfirmed = true; - exitDialog.Buttons.First().Click(); - } + exitDialog.PerformOkAction(); else - { dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); - return true; - } + + return true; } buttons.State = ButtonSystemState.Exit; From 44e22b31a905b18a35ca0f84282f581c5c908440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 16:28:25 +0900 Subject: [PATCH 174/429] Refactor editor exit sequence to avoid any scenario where a dialog could go stray --- osu.Game/Screens/Edit/Editor.cs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 5ac3401720..5e990ff81e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -473,25 +473,23 @@ namespace osu.Game.Screens.Edit { if (!exitConfirmed) { - // if the confirm dialog is already showing (or we can't show it, ie. in tests) exit without save. - if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog) + // dialog overlay may not be available in visual tests. + if (dialogOverlay == null) { confirmExit(); - return base.OnExiting(next); + return true; + } + + // if the dialog is already displayed, confirm exit with no save. + if (dialogOverlay.CurrentDialog is PromptForSaveDialog saveDialog) + { + saveDialog.PerformOkAction(); + return true; } if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(() => - { - confirmExit(); - this.Exit(); - }, () => - { - confirmExitWithSave(); - this.Exit(); - })); - + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; } } @@ -506,8 +504,10 @@ namespace osu.Game.Screens.Edit private void confirmExitWithSave() { - exitConfirmed = true; Save(); + + exitConfirmed = true; + this.Exit(); } private void confirmExit() @@ -529,6 +529,7 @@ namespace osu.Game.Screens.Edit } exitConfirmed = true; + this.Exit(); } private readonly Bindable clipboard = new Bindable(); From fc5987bf6978f565bf038d474650fb2409cf5ac8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 16:52:34 +0900 Subject: [PATCH 175/429] Refactor `DialogOverlay` logic to avoid dismissal potentially being unhandled --- osu.Game/Overlays/Dialog/PopupDialog.cs | 6 ++++++ osu.Game/Overlays/DialogOverlay.cs | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index df085b56f2..c04ad7afb5 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -95,6 +95,8 @@ namespace osu.Game.Overlays.Dialog } } + protected override bool StartHidden => true; + protected PopupDialog() { RelativeSizeAxes = Axes.Both; @@ -205,6 +207,10 @@ namespace osu.Game.Overlays.Dialog }, }, }; + + // It's important we start in a visible state so our state fires on hide, even before load. + // This is used by the DialogOverlay to know when the dialog was dismissed. + Show(); } /// diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 4cc17a4c14..bc3b0e6c9a 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -35,15 +35,16 @@ namespace osu.Game.Overlays public void Push(PopupDialog dialog) { - if (dialog == CurrentDialog) return; + if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return; + // if any existing dialog is being displayed, dismiss it before showing a new one. CurrentDialog?.Hide(); + CurrentDialog = dialog; + CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue); dialogContainer.Add(CurrentDialog); - CurrentDialog.Show(); - CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue); Show(); } From b33d5e7a7a4c0a906d0b13f44bdaf0366f9d3938 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 17:00:52 +0900 Subject: [PATCH 176/429] Add new test and assertions to existing `DialogOverlay` test --- .../UserInterface/TestSceneDialogOverlay.cs | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index cc4a57fb83..f5cba2c900 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -11,13 +13,20 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneDialogOverlay : OsuTestScene { - public TestSceneDialogOverlay() + private DialogOverlay overlay; + + [SetUpSteps] + public void SetUpSteps() { - DialogOverlay overlay; + AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); + } - Add(overlay = new DialogOverlay()); + [Test] + public void TestBasic() + { + TestPopupDialog dialog = null; - AddStep("dialog #1", () => overlay.Push(new TestPopupDialog + AddStep("dialog #1", () => overlay.Push(dialog = new TestPopupDialog { Icon = FontAwesome.Regular.TrashAlt, HeaderText = @"Confirm deletion of", @@ -37,7 +46,9 @@ namespace osu.Game.Tests.Visual.UserInterface }, })); - AddStep("dialog #2", () => overlay.Push(new TestPopupDialog + AddAssert("first dialog displayed", () => overlay.CurrentDialog == dialog); + + AddStep("dialog #2", () => overlay.Push(dialog = new TestPopupDialog { Icon = FontAwesome.Solid.Cog, HeaderText = @"What do you want to do with", @@ -70,6 +81,42 @@ namespace osu.Game.Tests.Visual.UserInterface }, }, })); + + AddAssert("second dialog displayed", () => overlay.CurrentDialog == dialog); + } + + [Test] + public void TestDismissBeforePush() + { + AddStep("dismissed dialog push", () => + { + overlay.Push(new TestPopupDialog + { + State = { Value = Visibility.Hidden } + }); + }); + + AddAssert("no dialog pushed", () => overlay.CurrentDialog == null); + } + + [Test] + public void TestDismissBeforePushViaButtonPress() + { + AddStep("dismissed dialog push", () => + { + TestPopupDialog dialog; + overlay.Push(dialog = new TestPopupDialog + { + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton { Text = @"OK" }, + }, + }); + + dialog.PerformOkAction(); + }); + + AddAssert("no dialog pushed", () => overlay.CurrentDialog == null); } private class TestPopupDialog : PopupDialog From cc5c702a92ddf6306e8b136699f299b1d1c7d484 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 17:31:47 +0900 Subject: [PATCH 177/429] 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 178/429] 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 9806d94b745348e7c44af36cc93b76401234d225 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 15 May 2021 17:56:19 +0300 Subject: [PATCH 179/429] Move beatmap skin info creation to static method at `IBeatmapSkin` --- osu.Game/Skinning/BeatmapSkinExtensions.cs | 16 ++++++++++++++++ osu.Game/Skinning/LegacyBeatmapSkin.cs | 5 +---- 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Skinning/BeatmapSkinExtensions.cs diff --git a/osu.Game/Skinning/BeatmapSkinExtensions.cs b/osu.Game/Skinning/BeatmapSkinExtensions.cs new file mode 100644 index 0000000000..18ef09c392 --- /dev/null +++ b/osu.Game/Skinning/BeatmapSkinExtensions.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.Game.Beatmaps; + +namespace osu.Game.Skinning +{ + public static class BeatmapSkinExtensions + { + public static SkinInfo CreateSkinInfo(BeatmapInfo beatmap) => new SkinInfo + { + Name = beatmap.ToString(), + Creator = beatmap.Metadata?.AuthorString, + }; + } +} diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 3ec205e897..5ee436e8bb 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning protected override bool UseCustomSampleBanks => true; public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, IStorageResourceProvider resources) - : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path) + : base(BeatmapSkinExtensions.CreateSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path) { // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer) Configuration.AllowDefaultComboColoursFallback = false; @@ -49,8 +49,5 @@ namespace osu.Game.Skinning return base.GetSample(sampleInfo); } - - private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => - new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; } } From a639132825f38ed9c159247a7ef7b6769b7254ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 17:58:28 +0900 Subject: [PATCH 180/429] Avoid doing any re-fetch on beatmap from test scenes --- osu.Game/Beatmaps/BeatmapManager.cs | 3 +-- osu.Game/Screens/Edit/Editor.cs | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5e975de77c..dadc0624b3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -286,11 +286,10 @@ namespace osu.Game.Beatmaps { if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) return previous; - if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - if (beatmapInfo.BeatmapSet.Files == null) + if (beatmapInfo.BeatmapSet.Files == null || beatmapInfo.ID == 0) { var info = beatmapInfo; beatmapInfo = QueryBeatmap(b => b.ID == info.ID); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 5e990ff81e..eafa5847a3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -497,7 +497,11 @@ namespace osu.Game.Screens.Edit ApplyToBackground(b => b.FadeColour(Color4.White, 500)); resetTrack(); - Beatmap.Value = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); + var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); + + // beatmap re-fetch may not be feasible in tests. + if (!(refetchedBeatmap is DummyWorkingBeatmap)) + Beatmap.Value = refetchedBeatmap; return base.OnExiting(next); } From 825d61e22c92646d3166f65d7a44ae631b8fb343 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 May 2021 12:06:09 +0300 Subject: [PATCH 181/429] Separate default beatmap skins from `DefaultSkin` --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/Skinning/BeatmapSkin.cs | 32 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Skinning/BeatmapSkin.cs diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 3576b149bf..ead8572c54 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps public bool SkinLoaded => skin.IsResultAvailable; public ISkin Skin => skin.Value; - protected virtual ISkin GetSkin() => new DefaultSkin(null); + protected virtual ISkin GetSkin() => new BeatmapSkin(BeatmapInfo); private readonly RecyclableLazy skin; public abstract Stream GetStream(string storagePath); diff --git a/osu.Game/Skinning/BeatmapSkin.cs b/osu.Game/Skinning/BeatmapSkin.cs new file mode 100644 index 0000000000..14b845faeb --- /dev/null +++ b/osu.Game/Skinning/BeatmapSkin.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.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Beatmaps; + +namespace osu.Game.Skinning +{ + /// + /// An empty implementation of a beatmap skin, serves as a temporary default for s. + /// + /// + /// This should be removed once becomes instantiable or a new skin type for osu!lazer beatmaps is defined. + /// + public class BeatmapSkin : Skin + { + public BeatmapSkin(BeatmapInfo beatmap) + : base(BeatmapSkinExtensions.CreateSkinInfo(beatmap), null) + { + } + + public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; + + public override IBindable GetConfig(TLookup lookup) => null; + + public override ISample GetSample(ISampleInfo sampleInfo) => null; + } +} From b2c736b42a52c91f8780ad00602a0dd18d1e6b83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 18:09:46 +0900 Subject: [PATCH 182/429] 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 183/429] 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 ec89a149dd752d242e1675a4462c8af4e360eb3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 May 2021 13:36:10 +0300 Subject: [PATCH 184/429] Add failing test case --- .../TestSceneCatchPlayerLegacySkin.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 64695153b5..a0b6b6dbe1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -1,7 +1,13 @@ // 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.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Skinning.Legacy; +using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests @@ -9,6 +15,38 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene { - protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + [Test] + public void TestUsingLegacySkin() + { + // check for the existence of a random legacy component to ensure using legacy skin. + // this should exist in LegacySkinPlayerTestScene but the weird transformer logic below needs to be "fixed" or otherwise first. + AddAssert("using legacy skin", () => this.ChildrenOfType().Any()); + } + + protected override Ruleset CreatePlayerRuleset() => new TestCatchRuleset(); + + private class TestCatchRuleset : CatchRuleset + { + public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestCatchLegacySkinTransformer(source); + } + + private class TestCatchLegacySkinTransformer : CatchLegacySkinTransformer + { + public TestCatchLegacySkinTransformer(ISkinSource source) + : base(source) + { + } + + public override Drawable GetDrawableComponent(ISkinComponent component) + { + var drawable = base.GetDrawableComponent(component); + if (drawable != null) + return drawable; + + // it shouldn't really matter whether to return null or return this, + // but returning null skips over the beatmap skin, so this needs to exist to test things properly. + return Source.GetDrawableComponent(component); + } + } } } From 6a3c58b9adfc6184f23ae0eefa6c39c38bbfdc47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 19:58:55 +0900 Subject: [PATCH 185/429] 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 186/429] 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 187/429] 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 188/429] 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 189/429] 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 190/429] 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 191/429] 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 192/429] 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 193/429] 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 194/429] 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 195/429] 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 434377aa52300a5af7698733722f462196434840 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 May 2021 21:35:06 +0300 Subject: [PATCH 196/429] Revert "Add failing test case" This reverts commit ec89a149dd752d242e1675a4462c8af4e360eb3e. --- .../TestSceneCatchPlayerLegacySkin.cs | 40 +------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index a0b6b6dbe1..64695153b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -1,13 +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.Linq; using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Skinning.Legacy; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests @@ -15,38 +9,6 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene { - [Test] - public void TestUsingLegacySkin() - { - // check for the existence of a random legacy component to ensure using legacy skin. - // this should exist in LegacySkinPlayerTestScene but the weird transformer logic below needs to be "fixed" or otherwise first. - AddAssert("using legacy skin", () => this.ChildrenOfType().Any()); - } - - protected override Ruleset CreatePlayerRuleset() => new TestCatchRuleset(); - - private class TestCatchRuleset : CatchRuleset - { - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestCatchLegacySkinTransformer(source); - } - - private class TestCatchLegacySkinTransformer : CatchLegacySkinTransformer - { - public TestCatchLegacySkinTransformer(ISkinSource source) - : base(source) - { - } - - public override Drawable GetDrawableComponent(ISkinComponent component) - { - var drawable = base.GetDrawableComponent(component); - if (drawable != null) - return drawable; - - // it shouldn't really matter whether to return null or return this, - // but returning null skips over the beatmap skin, so this needs to exist to test things properly. - return Source.GetDrawableComponent(component); - } - } + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); } } 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 197/429] 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 6fc06a10a15037bb4890fc2d35ffc46d74bf8df5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 May 2021 21:52:29 +0300 Subject: [PATCH 198/429] Add extensible test scene for beatmap skins fallback instead --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs new file mode 100644 index 0000000000..760fc5a139 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -0,0 +1,129 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics; +using osu.Framework.Lists; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Extensions; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Skinning.Legacy; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene + { + private ISkin currentBeatmapSkin; + + [Resolved] + private SkinManager skinManager { get; set; } + + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(); + + [Cached(typeof(HealthProcessor))] + private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + + protected override bool HasCustomSteps => true; + + [Test] + public void TestEmptyDefaultBeatmapSkinFallsBack() + { + CreateSkinTest(DefaultLegacySkin.Info, () => new TestWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)).Skin); + AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, () => skinManager.CurrentSkin.Value); + } + + protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func beatmapSkin) + { + CreateTest(() => + { + AddStep("setup skins", () => + { + skinManager.CurrentSkinInfo.Value = gameCurrentSkin; + currentBeatmapSkin = beatmapSkin(); + }); + }); + } + + protected void AssertComponentsFromExpectedSource(SkinnableTarget target, Func expectedSource) + { + AddAssert($"{target} from {expectedSource.GetType().Name}", () => + { + var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource().GetDrawableComponent(new SkinnableTargetComponent(target)); + + Add(expectedComponentsContainer); + expectedComponentsContainer?.UpdateSubTree(); + var expectedInfo = expectedComponentsContainer?.CreateSkinnableInfo(); + Remove(expectedComponentsContainer); + + var actualInfo = Player.ChildrenOfType().First(s => s.Target == target) + .ChildrenOfType().Single().CreateSkinnableInfo(); + + return almostEqual(actualInfo, expectedInfo, 2f); + }); + + static bool almostEqual(SkinnableInfo info, SkinnableInfo other, float positionTolerance) => + other != null + && info.Anchor == other.Anchor + && info.Origin == other.Origin + && Precision.AlmostEquals(info.Position, other.Position, positionTolerance) + && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer((s1, s2) => almostEqual(s1, s2, positionTolerance))); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin); + + protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset(); + + private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly ISkin beatmapSkin; + + public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin) + : base(beatmap, storyboard, referenceClock, audio) + { + this.beatmapSkin = beatmapSkin; + } + + protected override ISkin GetSkin() => beatmapSkin; + } + + private class TestOsuRuleset : OsuRuleset + { + public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(source); + + private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer + { + public TestOsuLegacySkinTransformer(ISkinSource source) + : base(source) + { + } + + public override Drawable GetDrawableComponent(ISkinComponent component) + { + var drawable = base.GetDrawableComponent(component); + if (drawable != null) + return drawable; + + // this isn't really supposed to make a difference from returning null, + // but it appears it does, returning null will skip over falling back to beatmap skin, + // while calling Source.GetDrawableComponent() doesn't. + return Source.GetDrawableComponent(component); + } + } + } + } +} From 71da9600009ce891be550e07be5cafda3b9ec065 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 May 2021 22:53:21 +0300 Subject: [PATCH 199/429] Extract assert step addition out of assertion method --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 760fc5a139..1d0caa86e2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -43,37 +43,34 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestEmptyDefaultBeatmapSkinFallsBack() { CreateSkinTest(DefaultLegacySkin.Info, () => new TestWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)).Skin); - AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, () => skinManager.CurrentSkin.Value); + AddAssert("hud from default legacy skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } - protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func beatmapSkin) + protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin) { CreateTest(() => { AddStep("setup skins", () => { skinManager.CurrentSkinInfo.Value = gameCurrentSkin; - currentBeatmapSkin = beatmapSkin(); + currentBeatmapSkin = getBeatmapSkin(); }); }); } - protected void AssertComponentsFromExpectedSource(SkinnableTarget target, Func expectedSource) + protected bool AssertComponentsFromExpectedSource(SkinnableTarget target, ISkin expectedSource) { - AddAssert($"{target} from {expectedSource.GetType().Name}", () => - { - var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource().GetDrawableComponent(new SkinnableTargetComponent(target)); + var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new SkinnableTargetComponent(target)); - Add(expectedComponentsContainer); - expectedComponentsContainer?.UpdateSubTree(); - var expectedInfo = expectedComponentsContainer?.CreateSkinnableInfo(); - Remove(expectedComponentsContainer); + Add(expectedComponentsContainer); + expectedComponentsContainer?.UpdateSubTree(); + var expectedInfo = expectedComponentsContainer?.CreateSkinnableInfo(); + Remove(expectedComponentsContainer); - var actualInfo = Player.ChildrenOfType().First(s => s.Target == target) - .ChildrenOfType().Single().CreateSkinnableInfo(); + var actualInfo = Player.ChildrenOfType().First(s => s.Target == target) + .ChildrenOfType().Single().CreateSkinnableInfo(); - return almostEqual(actualInfo, expectedInfo, 2f); - }); + return almostEqual(actualInfo, expectedInfo, 2f); static bool almostEqual(SkinnableInfo info, SkinnableInfo other, float positionTolerance) => other != null From 9f3ea150f58247f6de0c7ed14f04282e1ae72c4b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 May 2021 23:01:27 +0300 Subject: [PATCH 200/429] Fix legacy beatmap skins not falling back properly on HUD components --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 5ee436e8bb..9ff2238e4e 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -23,6 +24,25 @@ namespace osu.Game.Skinning Configuration.AllowDefaultComboColoursFallback = false; } + public override Drawable GetDrawableComponent(ISkinComponent component) + { + if (component is SkinnableTargetComponent targetComponent) + { + switch (targetComponent.Target) + { + case SkinnableTarget.MainHUDComponents: + // this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet. + // therefore keep the check here until fallback default legacy skin is supported. + if (!this.HasFont(LegacyFont.Score)) + return null; + + break; + } + } + + return base.GetDrawableComponent(component); + } + public override IBindable GetConfig(TLookup lookup) { switch (lookup) From 97c84998844240a63a3ec7b32f766d03d657a1f1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 May 2021 23:01:41 +0300 Subject: [PATCH 201/429] Add test coverage --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 1d0caa86e2..48b5e67814 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -46,6 +46,13 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("hud from default legacy skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } + [Test] + public void TestEmptyLegacyBeatmapSkinFallsBack() + { + CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null)); + AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); + } + protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin) { CreateTest(() => From 85a3027f1ba4107bee90ee3d2c7492d93b1546f4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 19 May 2021 13:58:41 -0700 Subject: [PATCH 202/429] 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 203/429] 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 204/429] 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 205/429] 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 206/429] 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 207/429] 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 208/429] 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 209/429] 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 210/429] 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 211/429] 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 212/429] 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 213/429] 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 214/429] 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 215/429] 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 dbfaaecd9cd004e86fbfd0c741b8100e001dbabe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:39:29 +0900 Subject: [PATCH 216/429] Reword comment to not mention tests driectly --- osu.Game/Screens/Edit/Editor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index eafa5847a3..986a4efb28 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -497,9 +497,11 @@ namespace osu.Game.Screens.Edit ApplyToBackground(b => b.FadeColour(Color4.White, 500)); resetTrack(); + // To update the game-wide beatmap with any changes, perform a re-fetch on exit. + // This is required as the editor makes its local changes via EditorBeatmap + // (which are not propagated outwards to a potentially cached WorkingBeatmap). var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); - // beatmap re-fetch may not be feasible in tests. if (!(refetchedBeatmap is DummyWorkingBeatmap)) Beatmap.Value = refetchedBeatmap; From d197a7f6f5f539ec2e75f1bf2e7935bc8642d9a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 15:39:45 +0900 Subject: [PATCH 217/429] 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 7980bdd384baef8e557f0f72523b878353b7dee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:41:58 +0900 Subject: [PATCH 218/429] Revert incorrectly changed requery code --- osu.Game/Beatmaps/BeatmapManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index dadc0624b3..5e975de77c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -286,10 +286,11 @@ namespace osu.Game.Beatmaps { if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) return previous; + if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - if (beatmapInfo.BeatmapSet.Files == null || beatmapInfo.ID == 0) + if (beatmapInfo.BeatmapSet.Files == null) { var info = beatmapInfo; beatmapInfo = QueryBeatmap(b => b.ID == info.ID); From 6beeb7f7c433d636e30cc46f56d03b585c71d647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 15:55:07 +0900 Subject: [PATCH 219/429] 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 220/429] 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 221/429] 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 fdbd421040efcbd9718d95841034471f930e6291 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 16:01:14 +0900 Subject: [PATCH 222/429] Fix editor tests failing due to empty files being specified --- .../Editing/TestSceneEditorBeatmapCreation.cs | 2 + osu.Game/Beatmaps/BeatmapManager.cs | 7 ++- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 - osu.Game/Tests/Visual/EditorTestScene.cs | 43 ++++++++++++++++++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 7584c74c71..dd5e01adbb 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.Editing protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; + protected override bool IsolateSavingFromDatabase => false; + [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5e975de77c..d5dc70317c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -240,10 +241,12 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { var setInfo = info.BeatmapSet; + Debug.Assert(setInfo.Files != null); + using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) @@ -282,7 +285,7 @@ namespace osu.Game.Beatmaps /// The beatmap to lookup. /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches /// A instance correlating to the provided . - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) + public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) return previous; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index fa6dc5647d..2717146c99 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -29,7 +29,6 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.Ruleset = ruleset; BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; - BeatmapInfo.BeatmapSet.Files = new List(); BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new BeatmapOnlineInfo(); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index a9ee8e2668..de7fdd0cf8 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -4,11 +4,18 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IO.Archives; +using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -20,10 +27,20 @@ namespace osu.Game.Tests.Visual protected EditorClock EditorClock { get; private set; } + /// + /// Whether any saves performed by the editor should be isolate (and not persist) to the underlying . + /// + protected virtual bool IsolateSavingFromDatabase => true; + [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { - Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); + var working = CreateWorkingBeatmap(Ruleset.Value); + + Beatmap.Value = working; + + if (IsolateSavingFromDatabase) + Dependencies.CacheAs(new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default, working)); } protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true @@ -65,5 +82,27 @@ namespace osu.Game.Tests.Visual public new bool HasUnsavedChanges => base.HasUnsavedChanges; } + + private class TestBeatmapManager : BeatmapManager + { + private readonly WorkingBeatmap testBeatmap; + + public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap) + : base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, false) + { + this.testBeatmap = testBeatmap; + } + + protected override string ComputeHash(BeatmapSetInfo item, ArchiveReader reader = null) + => string.Empty; + + public override WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) + => testBeatmap; + + public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + { + // don't actually care about saving for this context. + } + } } } From 750a5c3ea96530fae27195fd5afb0fe89ff4dd0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 17:20:30 +0900 Subject: [PATCH 223/429] 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 c24712642cc567f158f2d4a7b559429567347d8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 17:16:38 +0900 Subject: [PATCH 224/429] Make `BeatmapSetInfo.Files` non-nullable --- osu.Game/Beatmaps/BeatmapManager.cs | 22 ++++++++++------------ osu.Game/Beatmaps/BeatmapSetInfo.cs | 10 ++++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d5dc70317c..18fbd1f5c2 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -245,8 +244,6 @@ namespace osu.Game.Beatmaps { var setInfo = info.BeatmapSet; - Debug.Assert(setInfo.Files != null); - using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) @@ -287,20 +284,21 @@ namespace osu.Game.Beatmaps /// A instance correlating to the provided . public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { - if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) - return previous; - - if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) + if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; - if (beatmapInfo.BeatmapSet.Files == null) + if (beatmapInfo == DefaultBeatmap?.BeatmapInfo) + return DefaultBeatmap; + + // if there are no files, presume the full beatmap info has not yet been fetched from the database. + if (beatmapInfo.BeatmapSet.Files.Count == 0) { - var info = beatmapInfo; - beatmapInfo = QueryBeatmap(b => b.ID == info.ID); + int lookupId = beatmapInfo.ID; + beatmapInfo = QueryBeatmap(b => b.ID == lookupId); } - if (beatmapInfo == null) - return DefaultBeatmap; + if (beatmapInfo.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) + return previous; lock (workingCache) { diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 1ce42535a0..3b1ff4ced0 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Testing; using osu.Game.Database; @@ -31,6 +32,9 @@ namespace osu.Game.Beatmaps public List Beatmaps { get; set; } + [NotNull] + public List Files { get; set; } = new List(); + [NotMapped] public BeatmapSetOnlineInfo OnlineInfo { get; set; } @@ -57,16 +61,14 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + public string StoryboardFile => Files.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. /// The path returned is relative to the user file storage. /// /// The name of the file to get the storage path of. - public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - - public List Files { get; set; } + public string GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; public override string ToString() => Metadata?.ToString() ?? base.ToString(); From 9d07749959aa346be7106918e9411999e1cd21d9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 May 2021 17:41:46 +0900 Subject: [PATCH 225/429] 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 226/429] 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 227/429] 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 228/429] 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 229/429] 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 230/429] 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 231/429] 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 232/429] 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 233/429] 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 234/429] 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 235/429] 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 236/429] 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 237/429] 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 238/429] 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 239/429] 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 240/429] 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 241/429] 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 242/429] 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 243/429] 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 262a27610c94dbaae44925e88534166b34318fd9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 20 May 2021 20:47:40 +0300 Subject: [PATCH 244/429] Improve components assertion logic --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 1d0caa86e2..dd77a71ca5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -7,7 +7,9 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Lists; +using osu.Framework.Logging; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Framework.Utils; @@ -60,24 +62,41 @@ namespace osu.Game.Tests.Visual.Gameplay protected bool AssertComponentsFromExpectedSource(SkinnableTarget target, ISkin expectedSource) { + var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target) + .ChildrenOfType().SingleOrDefault(); + + if (actualComponentsContainer == null) + return false; + + var actualInfo = actualComponentsContainer.CreateSkinnableInfo(); + var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new SkinnableTargetComponent(target)); + if (expectedComponentsContainer == null) + return false; - Add(expectedComponentsContainer); - expectedComponentsContainer?.UpdateSubTree(); + var expectedComponentsAdjustmentContainer = new Container + { + Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content), + Size = actualComponentsContainer.DrawSize, + Child = expectedComponentsContainer, + }; + + Add(expectedComponentsAdjustmentContainer); + expectedComponentsAdjustmentContainer?.UpdateSubTree(); var expectedInfo = expectedComponentsContainer?.CreateSkinnableInfo(); - Remove(expectedComponentsContainer); + Remove(expectedComponentsAdjustmentContainer); - var actualInfo = Player.ChildrenOfType().First(s => s.Target == target) - .ChildrenOfType().Single().CreateSkinnableInfo(); + return almostEqual(actualInfo, expectedInfo); - return almostEqual(actualInfo, expectedInfo, 2f); - - static bool almostEqual(SkinnableInfo info, SkinnableInfo other, float positionTolerance) => + static bool almostEqual(SkinnableInfo info, SkinnableInfo other) => other != null + && info.Type == other.Type && info.Anchor == other.Anchor && info.Origin == other.Origin - && Precision.AlmostEquals(info.Position, other.Position, positionTolerance) - && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer((s1, s2) => almostEqual(s1, s2, positionTolerance))); + && Precision.AlmostEquals(info.Position, other.Position) + && Precision.AlmostEquals(info.Scale, other.Scale) + && Precision.AlmostEquals(info.Rotation, other.Rotation) + && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) From eaae9a1b678ae64b3c680c7ff1d172205b122396 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 20 May 2021 21:08:31 +0300 Subject: [PATCH 245/429] Remove unrequired null conditional --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index dd77a71ca5..ecdb6fa4af 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Gameplay }; Add(expectedComponentsAdjustmentContainer); - expectedComponentsAdjustmentContainer?.UpdateSubTree(); - var expectedInfo = expectedComponentsContainer?.CreateSkinnableInfo(); + expectedComponentsAdjustmentContainer.UpdateSubTree(); + var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo(); Remove(expectedComponentsAdjustmentContainer); return almostEqual(actualInfo, expectedInfo); From c0dfe379655d9e68496d0de3a6182a764c1f904f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 20 May 2021 21:08:36 +0300 Subject: [PATCH 246/429] Remove unused using directive --- osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index ecdb6fa4af..22959c082a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; -using osu.Framework.Logging; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Framework.Utils; 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/429] 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/429] 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/429] 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/429] 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 49aa0f3a180cd203d6ca1031b0a7ef8d3c7c4deb Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 10:04:45 +0700 Subject: [PATCH 251/429] initial main page --- .../Visual/Online/TestSceneWikiMainPage.cs | 36 +++++++++++++++++++ osu.Game/Overlays/Wiki/WikiMainPage.cs | 17 +++++++++ 2 files changed, 53 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs create mode 100644 osu.Game/Overlays/Wiki/WikiMainPage.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs new file mode 100644 index 0000000000..e193f5b98e --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osu.Game.Overlays.Wiki; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneWikiMainPage : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); + + public TestSceneWikiMainPage() + { + Children = new Drawable[] + { + new Box + { + Colour = overlayColour.Background5, + RelativeSizeAxes = Axes.Both, + }, + new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Child = new WikiMainPage(), + } + }; + } + } +} diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs new file mode 100644 index 0000000000..1ae1631a6e --- /dev/null +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -0,0 +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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Overlays.Wiki +{ + public class WikiMainPage : FillFlowContainer + { + public WikiMainPage() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + } +} From 5964ee23cb2fb95bbecd94115b10d55bfe0344fb Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 09:54:54 +0700 Subject: [PATCH 252/429] add HtmlAgilityPack dependency --- osu.Game/osu.Game.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fa2945db6a..e3331cd365 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,6 +19,7 @@ + From 216b87691c203c35f76263859dfbe56dda9ab5d4 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 10:04:45 +0700 Subject: [PATCH 253/429] add blurb --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 1ae1631a6e..cb6c80cb73 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -3,15 +3,56 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using System.Linq; +using HtmlAgilityPack; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Wiki { public class WikiMainPage : FillFlowContainer { + public string Markdown; + public WikiMainPage() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; } + + [BackgroundDependencyLoader] + private void load() + { + var html = new HtmlDocument(); + html.LoadHtml(Markdown); + + Children = new Drawable[] + { + createBlurb(html) + }; + } + + private Container createBlurb(HtmlDocument html) + { + var blurbNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page__blurb')]").First(); + + return new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Vertical = 30, + }, + Child = new OsuSpriteText + { + Text = blurbNode.InnerText, + Font = OsuFont.GetFont(size: 12), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + }; + } } } From 895eb14c5ae222877b49cd322409f0ce3736f927 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 14:09:30 +0900 Subject: [PATCH 254/429] 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 255/429] 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 256/429] 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 257/429] 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 258/429] 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 259/429] 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 260/429] 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 261/429] 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 262/429] 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 263/429] 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 264/429] 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 265/429] 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 266/429] 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 267/429] 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 268/429] 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 269/429] 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 270/429] 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 271/429] 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 272/429] 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 318e5fc60bf5455653e8e2ae9846360b0b531542 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 May 2021 20:14:26 +0300 Subject: [PATCH 273/429] Mark `WorkingBeatmap.GetSkin()` as abstract --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index ead8572c54..0a55678fb7 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps public bool SkinLoaded => skin.IsResultAvailable; public ISkin Skin => skin.Value; - protected virtual ISkin GetSkin() => new BeatmapSkin(BeatmapInfo); + protected abstract ISkin GetSkin(); private readonly RecyclableLazy skin; public abstract Stream GetStream(string storagePath); From 04e75d8f2b19ab70dd32a49922dc1157e304fb2b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 May 2021 20:16:51 +0300 Subject: [PATCH 274/429] Return empty skin for `GetSkin()` in `TestWorkingBeatmap` --- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 852006bc9b..2e4a9fa28f 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -3,9 +3,15 @@ using System.IO; using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Skinning; using osu.Game.Storyboards; namespace osu.Game.Tests.Beatmaps @@ -36,10 +42,23 @@ namespace osu.Game.Tests.Beatmaps protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard(); + protected override ISkin GetSkin() => new EmptySkin(); + public override Stream GetStream(string storagePath) => null; protected override Texture GetBackground() => null; protected override Track GetBeatmapTrack() => null; + + private class EmptySkin : ISkin + { + public Drawable GetDrawableComponent(ISkinComponent component) => null; + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; + + public ISample GetSample(ISampleInfo sampleInfo) => null; + + public IBindable GetConfig(TLookup lookup) => null; + } } } From 4f6de6fdc66a0e18aad8f2640dbaa848ea8c8888 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 May 2021 20:21:00 +0300 Subject: [PATCH 275/429] Implement `GetSkin()` for other working beatmaps --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 ++ osu.Game.Tests/WaveformTestBeatmap.cs | 3 +++ osu.Game/Beatmaps/BeatmapManager.cs | 1 + osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 +++ osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 3 +++ osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 3 +++ 6 files changed, 15 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index a18f82fe4a..059432eeaf 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -169,6 +169,8 @@ namespace osu.Game.Tests.Beatmaps.Formats protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + protected override ISkin GetSkin() => throw new NotImplementedException(); + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); } } diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index cbed28641c..5477e4a0f8 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -52,6 +53,8 @@ namespace osu.Game.Tests protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); + protected override ISkin GetSkin() => null; + public override Stream GetStream(string storagePath) => null; protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5e975de77c..46e3a4f6d7 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -526,6 +526,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; protected override Track GetBeatmapTrack() => null; + protected override ISkin GetSkin() => null; public override Stream GetStream(string storagePath) => null; } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 6922d1c286..ea7f45e53f 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; namespace osu.Game.Beatmaps { @@ -49,6 +50,8 @@ namespace osu.Game.Beatmaps protected override Track GetBeatmapTrack() => GetVirtualTrack(); + protected override ISkin GetSkin() => null; + public override Stream GetStream(string storagePath) => null; private class DummyRulesetInfo : RulesetInfo diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 66784fda54..6f92db98ee 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -11,6 +11,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.IO; +using osu.Game.Skinning; using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Screens.Edit @@ -117,6 +118,8 @@ namespace osu.Game.Screens.Edit protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + protected override ISkin GetSkin() => throw new NotImplementedException(); + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index a97f6defe9..4935f7fc13 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -17,6 +17,7 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; namespace osu.Game.Tests.Beatmaps { @@ -216,6 +217,8 @@ namespace osu.Game.Tests.Beatmaps protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + protected override ISkin GetSkin() => throw new NotImplementedException(); + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) From afb33f1641a924b04bd28f3cf52af74c7d730af1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 May 2021 20:21:50 +0300 Subject: [PATCH 276/429] Remove no longer necessary test case --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 22959c082a..bece80903f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; @@ -21,7 +20,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; -using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { @@ -40,13 +38,6 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; - [Test] - public void TestEmptyDefaultBeatmapSkinFallsBack() - { - CreateSkinTest(DefaultLegacySkin.Info, () => new TestWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)).Skin); - AddAssert("hud from default legacy skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); - } - protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin) { CreateTest(() => From cd3f5433942133703acaa0822b9d444efc9218ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 May 2021 02:32:55 +0900 Subject: [PATCH 277/429] 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 278/429] 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 279/429] 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 280/429] 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 281/429] 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 282/429] 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 283/429] 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 b8a5b5aaf8b7097f0b1a0be57156a5fd15aefa0a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 17:22:31 +0700 Subject: [PATCH 284/429] add test for image block and inline image --- .../Online/TestSceneWikiMarkdownContainer.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 3731e7a782..67030631b0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -120,6 +120,29 @@ needs_cleanup: true }); } + [Test] + public void TestBlockImage() + { + AddStep("Add paragraph with block image", () => + { + markdownContainer.CurrentPath = "Interface/"; + markdownContainer.Text = @"Line before image + +![play menu](img/play-menu.jpg ""Main Menu in osu!"") + +Line after image"; + }); + } + + [Test] + public void TestInlineImage() + { + AddStep("Add inline image", () => + { + markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; + }); + } + private class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 0448f6fdb362dcfe370041738f27395949a601fa Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 25 May 2021 13:21:08 +0700 Subject: [PATCH 285/429] add main page markdown --- osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs index e193f5b98e..3a2bafb128 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs @@ -28,9 +28,16 @@ namespace osu.Game.Tests.Visual.Online { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(20), - Child = new WikiMainPage(), + Child = new WikiMainPage + { + Markdown = main_page_markdown + } } }; } + + // From https://osu.ppy.sh/api/v2/wiki/en/Main_Page + private const string main_page_markdown = + "---\nlayout: main_page\n---\n\n\n\n
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; } } From 4a543c25575fe80982ac296a5b3af2e17c1ed61a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 17:49:26 +0700 Subject: [PATCH 286/429] add simple panels --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index cb6c80cb73..258f6a4f19 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -6,13 +6,23 @@ using osu.Framework.Graphics.Containers; using System.Linq; using HtmlAgilityPack; using osu.Framework.Allocation; +using System.Collections.Generic; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Wiki.Markdown; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Wiki { public class WikiMainPage : FillFlowContainer { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + public string Markdown; public WikiMainPage() @@ -31,6 +41,7 @@ namespace osu.Game.Overlays.Wiki { createBlurb(html) }; + AddRange(createPanels(html)); } private Container createBlurb(HtmlDocument html) @@ -54,5 +65,50 @@ namespace osu.Game.Overlays.Wiki } }; } + + private IEnumerable createPanels(HtmlDocument html) + { + var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]"); + + foreach (var panel in panelsNode) + { + var isFullWidth = panel.HasClass("wiki-main-page-panel--full"); + + yield return new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = isFullWidth ? 1.0f : 0.5f, + Padding = new MarginPadding(3), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(25), + Offset = new Vector2(0, 1), + Radius = 3, + }, + Child = new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + }, + new WikiMarkdownContainer + { + Text = panel.InnerText, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + }; + } + } } } From d9e898a2eea4ee56508255ce9988be9a9978aa0a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 17:56:27 +0700 Subject: [PATCH 287/429] extract WikiPanelContainer --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 44 ++------------- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 56 ++++++++++++++++++++ 2 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 osu.Game/Overlays/Wiki/WikiPanelContainer.cs diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 258f6a4f19..bdd0b4dc11 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -1,28 +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 System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using System.Linq; using HtmlAgilityPack; using osu.Framework.Allocation; -using System.Collections.Generic; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Wiki.Markdown; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Wiki { public class WikiMainPage : FillFlowContainer { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - public string Markdown; public WikiMainPage() @@ -74,39 +65,12 @@ namespace osu.Game.Overlays.Wiki { var isFullWidth = panel.HasClass("wiki-main-page-panel--full"); - yield return new Container + yield return new WikiPanelContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Width = isFullWidth ? 1.0f : 0.5f, - Padding = new MarginPadding(3), - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(25), - Offset = new Vector2(0, 1), - Radius = 3, - }, - Child = new Box - { - Colour = colourProvider.Background4, - RelativeSizeAxes = Axes.Both, - }, - }, - new WikiMarkdownContainer - { - Text = panel.InnerText, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - } + Text = panel.InnerText, }; } } diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs new file mode 100644 index 0000000000..33c9369ccb --- /dev/null +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -0,0 +1,56 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays.Wiki.Markdown; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Wiki +{ + public class WikiPanelContainer : Container + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public string Text; + + [BackgroundDependencyLoader] + private void load() + { + Padding = new MarginPadding(3); + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(25), + Offset = new Vector2(0, 1), + Radius = 3, + }, + Child = new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + }, + new WikiMarkdownContainer + { + Text = Text, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }; + } + } +} From 4d222467ccb6d580de5f86b849058153fdaae745 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 18:00:54 +0700 Subject: [PATCH 288/429] initial WikiPanelMarkdownContainer --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index 33c9369ccb..2d5d806b12 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Wiki RelativeSizeAxes = Axes.Both, }, }, - new WikiMarkdownContainer + new WikiPanelMarkdownContainer { Text = Text, RelativeSizeAxes = Axes.X, @@ -52,5 +52,15 @@ namespace osu.Game.Overlays.Wiki } }; } + + private class WikiPanelMarkdownContainer : WikiMarkdownContainer + { + public WikiPanelMarkdownContainer() + { + LineSpacing = 0; + DocumentPadding = new MarginPadding(30); + DocumentMargin = new MarginPadding(0); + } + } } } From 365a0b25f28a2bc7043816dde0b1046d52cc0e36 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 18:03:38 +0700 Subject: [PATCH 289/429] add IsFullWidth --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 1 + osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index bdd0b4dc11..c013cbec59 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -71,6 +71,7 @@ namespace osu.Game.Overlays.Wiki AutoSizeAxes = Axes.Y, Width = isFullWidth ? 1.0f : 0.5f, Text = panel.InnerText, + IsFullWidth = isFullWidth, }; } } diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index 2d5d806b12..b58ba9bb09 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -20,6 +20,8 @@ namespace osu.Game.Overlays.Wiki public string Text; + public bool IsFullWidth; + [BackgroundDependencyLoader] private void load() { @@ -49,12 +51,15 @@ namespace osu.Game.Overlays.Wiki Text = Text, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + IsFullWidth = IsFullWidth, } }; } private class WikiPanelMarkdownContainer : WikiMarkdownContainer { + public bool IsFullWidth; + public WikiPanelMarkdownContainer() { LineSpacing = 0; From 1e5f34567ae2f28598c8f08f355c84dffebd3a97 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 18:07:27 +0700 Subject: [PATCH 290/429] add panel heading main page --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index b58ba9bb09..00afb7b062 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.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 Markdig.Syntax; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays.Wiki.Markdown; using osuTK; using osuTK.Graphics; @@ -66,6 +70,33 @@ namespace osu.Game.Overlays.Wiki DocumentPadding = new MarginPadding(30); DocumentMargin = new MarginPadding(0); } + + protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock) + { + IsFullWidth = IsFullWidth, + }; + } + + private class WikiPanelHeading : OsuMarkdownHeading + { + public bool IsFullWidth; + + public WikiPanelHeading(HeadingBlock headingBlock) + : base(headingBlock) + { + Margin = new MarginPadding { Bottom = 40 }; + } + + public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => + { + f.Anchor = Anchor.TopCentre; + f.Origin = Anchor.TopCentre; + f.TextAnchor = Anchor.TopCentre; + }); + + protected override FontWeight GetFontWeightByLevel(int level) => FontWeight.Regular; + + protected override float GetFontSizeByLevel(int level) => base.GetFontSizeByLevel(IsFullWidth ? level : 3); } } } From 512d6d2f7fd7b2435b8e4920a59875df6c8c85d1 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 18:09:09 +0700 Subject: [PATCH 291/429] centering link inside panel --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index 00afb7b062..31b939d2ac 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -71,6 +71,8 @@ namespace osu.Game.Overlays.Wiki DocumentMargin = new MarginPadding(0); } + public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre); + protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock) { IsFullWidth = IsFullWidth, From cd211de7295f3dfb3464f0dcd29054423ea2ac86 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 18:10:56 +0700 Subject: [PATCH 292/429] make panel text bold --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index 31b939d2ac..c5dd8549da 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays.Wiki.Markdown; @@ -71,6 +72,8 @@ namespace osu.Game.Overlays.Wiki DocumentMargin = new MarginPadding(0); } + public override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: FontWeight.Bold)); + public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre); protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock) From 40688810966e0ecb29cc05fc7f17f09d26e77606 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 18:11:54 +0700 Subject: [PATCH 293/429] add bottom margin for paragraph --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index c5dd8549da..727cae766d 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -76,6 +76,9 @@ namespace osu.Game.Overlays.Wiki public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre); + protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) + => base.CreateParagraph(paragraphBlock, level).With(p => p.Margin = new MarginPadding { Bottom = 10 }); + protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock) { IsFullWidth = IsFullWidth, From dc322d1c63f33712d0d4a1488bda78b0d4222448 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 May 2021 20:22:48 +0900 Subject: [PATCH 294/429] Run all type and sample mutations through standardising methods --- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 21 +++++---------- .../Objects/TaikoHitObject.cs | 26 +++++++++++++++++++ .../Objects/TaikoStrongableHitObject.cs | 22 +++++++++++----- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index f4a66c39a8..8ede21fdad 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -2,30 +2,23 @@ // 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 { public class Hit : TaikoStrongableHitObject { - public readonly Bindable TypeBindable = new Bindable(); - - /// - /// The that actuates this . - /// - public HitType Type + protected override void UpdateTypeFromSamples() { - get => TypeBindable.Value; - set - { - TypeBindable.Value = value; - updateSamplesFromType(); - } + base.UpdateTypeFromSamples(); + + Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre; } - private void updateSamplesFromType() + protected override void UpdateSamplesFromType() { + base.UpdateSamplesFromType(); + var rimSamples = getRimSamples(); bool isRimType = Type == HitType.Rim; diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index f047c03f4b..46b864e7de 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -11,6 +12,17 @@ namespace osu.Game.Rulesets.Taiko.Objects { public abstract class TaikoHitObject : HitObject { + public readonly Bindable TypeBindable = new Bindable(); + + /// + /// The that actuates this . + /// + public HitType Type + { + get => TypeBindable.Value; + set => TypeBindable.Value = value; + } + /// /// Default size of a drawable taiko hit object. /// @@ -19,5 +31,19 @@ namespace osu.Game.Rulesets.Taiko.Objects public override Judgement CreateJudgement() => new TaikoJudgement(); protected override HitWindows CreateHitWindows() => new TaikoHitWindows(); + + protected TaikoHitObject() + { + SamplesBindable.BindCollectionChanged((_, __) => UpdateTypeFromSamples()); + TypeBindable.BindValueChanged(_ => UpdateSamplesFromType()); + } + + protected virtual void UpdateSamplesFromType() + { + } + + protected virtual void UpdateTypeFromSamples() + { + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index cac56d1269..237000474d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -33,15 +33,25 @@ namespace osu.Game.Rulesets.Taiko.Objects public bool IsStrong { get => IsStrongBindable.Value; - set - { - IsStrongBindable.Value = value; - updateSamplesFromStrong(); - } + set => IsStrongBindable.Value = value; } - private void updateSamplesFromStrong() + protected TaikoStrongableHitObject() { + IsStrongBindable.BindValueChanged(_ => UpdateSamplesFromType()); + } + + protected override void UpdateTypeFromSamples() + { + base.UpdateTypeFromSamples(); + + IsStrong = getStrongSamples().Any(); + } + + protected override void UpdateSamplesFromType() + { + base.UpdateSamplesFromType(); + var strongSamples = getStrongSamples(); if (IsStrongBindable.Value != strongSamples.Any()) From 8389d90f7ee1d1a6edb3197201c625700cac9907 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 18:52:20 +0700 Subject: [PATCH 295/429] add GridContainer in wiki main page --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index c013cbec59..2da5e5e928 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -30,9 +30,13 @@ namespace osu.Game.Overlays.Wiki Children = new Drawable[] { - createBlurb(html) + createBlurb(html), + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, }; - AddRange(createPanels(html)); } private Container createBlurb(HtmlDocument html) From 24fef221e304ac4d0f1c9fff9b551c8f5d11359f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 19:07:10 +0700 Subject: [PATCH 296/429] change createPanels method to create grid content --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 48 ++++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 2da5e5e928..12c17d5225 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -61,22 +61,48 @@ namespace osu.Game.Overlays.Wiki }; } - private IEnumerable createPanels(HtmlDocument html) + private IEnumerable createPanels(HtmlDocument html) { - var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]"); + var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]").ToArray(); - foreach (var panel in panelsNode) + for (var i = 0; i < panelsNode.Length; i++) { - var isFullWidth = panel.HasClass("wiki-main-page-panel--full"); + var isFullWidth = panelsNode[i].HasClass("wiki-main-page-panel--full"); - yield return new WikiPanelContainer + if (isFullWidth) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = isFullWidth ? 1.0f : 0.5f, - Text = panel.InnerText, - IsFullWidth = isFullWidth, - }; + yield return new Drawable[] + { + new WikiPanelContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = panelsNode[i].InnerText, + IsFullWidth = true, + Width = 2, + }, + null, + }; + } + + if (i % 2 == 1) + { + yield return new Drawable[] + { + new WikiPanelContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = panelsNode[i].InnerText, + }, + new WikiPanelContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = panelsNode[i + 1].InnerText, + }, + }; + } } } } From 10c4ba3a746ac11b9899ce5c57523f0830bbaa2d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 19:07:22 +0700 Subject: [PATCH 297/429] add panels to grid content --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 12c17d5225..c4e43d1438 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.Wiki var html = new HtmlDocument(); html.LoadHtml(Markdown); + var panels = createPanels(html).ToArray(); + Children = new Drawable[] { createBlurb(html), @@ -35,6 +37,8 @@ namespace osu.Game.Overlays.Wiki { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + RowDimensions = Enumerable.Repeat(new Dimension(GridSizeMode.AutoSize), panels.Length).ToArray(), + Content = panels, }, }; } From bbfd7ea23f622f96bcec0f0d4713068bd1eda920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 May 2021 21:20:08 +0900 Subject: [PATCH 298/429] 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 4c9d72e62ae4d82404aa69a0b0d34b81db4b93a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 May 2021 21:19:38 +0900 Subject: [PATCH 299/429] Ensure `EditorBeatmap.Update` is called inside `PerformOnSelection` calls --- osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs | 6 +++++- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 8 +++++++- .../Edit/Compose/Components/EditorSelectionHandler.cs | 7 ++++++- .../Components/Timeline/TimelineBlueprintContainer.cs | 6 +++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index a24130d6ac..ab3b729307 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -69,7 +69,11 @@ namespace osu.Game.Rulesets.Taiko.Edit { EditorBeatmap.PerformOnSelection(h => { - if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre; + if (h is Hit taikoHit) + { + taikoHit.Type = state ? HitType.Rim : HitType.Centre; + EditorBeatmap.Update(h); + } }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 5a6f98f504..22b211f257 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -77,7 +77,13 @@ namespace osu.Game.Screens.Edit.Compose.Components double offset = result.Time.Value - blueprints.First().Item.StartTime; if (offset != 0) - Beatmap.PerformOnSelection(obj => obj.StartTime += offset); + { + Beatmap.PerformOnSelection(obj => + { + obj.StartTime += offset; + Beatmap.Update(obj); + }); + } } return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 2141c490df..246d4aa8d7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -125,6 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return; h.Samples.Add(new HitSampleInfo(sampleName)); + EditorBeatmap.Update(h); }); } @@ -134,7 +135,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); + EditorBeatmap.PerformOnSelection(h => + { + h.SamplesBindable.RemoveAll(s => s.Name == sampleName); + EditorBeatmap.Update(h); + }); } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 7c1bbd65f9..6f04f36b83 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -276,7 +276,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime); double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount; - EditorBeatmap.PerformOnSelection(h => h.StartTime += adjustment); + EditorBeatmap.PerformOnSelection(h => + { + h.StartTime += adjustment; + EditorBeatmap.Update(h); + }); } } From 6751d79ce8ef94b465360384c7d8ffbaca84330c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 May 2021 16:21:27 +0300 Subject: [PATCH 300/429] 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 301/429] 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 24960c4fb83cfea649ce22a04767abfefc748431 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 21:31:49 +0700 Subject: [PATCH 302/429] move panel spacing to ctor --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 6 ------ osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 8 +++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index c4e43d1438..8ff85c1404 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -79,8 +79,6 @@ namespace osu.Game.Overlays.Wiki { new WikiPanelContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, Text = panelsNode[i].InnerText, IsFullWidth = true, Width = 2, @@ -95,14 +93,10 @@ namespace osu.Game.Overlays.Wiki { new WikiPanelContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, Text = panelsNode[i].InnerText, }, new WikiPanelContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, Text = panelsNode[i + 1].InnerText, }, }; diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index 727cae766d..64f4d9fbcd 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -27,10 +27,16 @@ namespace osu.Game.Overlays.Wiki public bool IsFullWidth; + public WikiPanelContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding(3); + } + [BackgroundDependencyLoader] private void load() { - Padding = new MarginPadding(3); Children = new Drawable[] { new Container From bd1454bdd1c26de28c5ec91cf6ed89dec218bc94 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 21:42:58 +0700 Subject: [PATCH 303/429] update height to max of its parent height --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index 64f4d9fbcd..f0fecce8dc 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.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 Markdig.Syntax; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -23,6 +24,8 @@ namespace osu.Game.Overlays.Wiki [Resolved] private OverlayColourProvider colourProvider { get; set; } + private WikiPanelMarkdownContainer panelContainer; + public string Text; public bool IsFullWidth; @@ -30,7 +33,6 @@ namespace osu.Game.Overlays.Wiki public WikiPanelContainer() { RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; Padding = new MarginPadding(3); } @@ -57,7 +59,7 @@ namespace osu.Game.Overlays.Wiki RelativeSizeAxes = Axes.Both, }, }, - new WikiPanelMarkdownContainer + panelContainer = new WikiPanelMarkdownContainer { Text = Text, RelativeSizeAxes = Axes.X, @@ -67,6 +69,12 @@ namespace osu.Game.Overlays.Wiki }; } + protected override void Update() + { + base.Update(); + Height = Math.Max(panelContainer.Height, Parent.DrawHeight); + } + private class WikiPanelMarkdownContainer : WikiMarkdownContainer { public bool IsFullWidth; From 222c34c0a1c77113a0ceb128f37db1acfab547e0 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 23 May 2021 21:46:41 +0700 Subject: [PATCH 304/429] fix heading font weight to light --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index f0fecce8dc..71b492b375 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Wiki f.TextAnchor = Anchor.TopCentre; }); - protected override FontWeight GetFontWeightByLevel(int level) => FontWeight.Regular; + protected override FontWeight GetFontWeightByLevel(int level) => FontWeight.Light; protected override float GetFontSizeByLevel(int level) => base.GetFontSizeByLevel(IsFullWidth ? level : 3); } From 593fea0d5fe8ae8a9286a41b967833fab1443c2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 13:14:18 +0900 Subject: [PATCH 305/429] 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 306/429] 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 a69a1b521105654d716a7876b279a0d0a1322bd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 13:53:51 +0900 Subject: [PATCH 307/429] Fix `Player` potentially running `MakeCurrent` when already removed from the screen stack Closes #12919. --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 39f9e2d388..ee940fae40 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -519,7 +519,7 @@ namespace osu.Game.Screens.Play // there is a chance that the exit was performed after the transition to results has started. // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). - if (!this.IsCurrentScreen()) + if (!this.IsCurrentScreen() && this.GetChildScreen() != null) { ValidForResume = false; this.MakeCurrent(); From 7494ddeef40293f989c412e51438e450dd93997b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 May 2021 14:07:40 +0900 Subject: [PATCH 308/429] 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 06fe0563d30d515a40729c7946ed326c41292ac7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 24 May 2021 08:26:44 +0300 Subject: [PATCH 309/429] Move GetNewsRequest from ArticleListing to NewsOverlay --- .../Overlays/News/Displays/ArticleListing.cs | 46 ++++------------ osu.Game/Overlays/NewsOverlay.cs | 54 ++++++++++++++++--- 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index b49326a1f1..b554b462a9 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -8,9 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; 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 @@ -20,26 +18,20 @@ namespace osu.Game.Overlays.News.Displays /// public class ArticleListing : CompositeDrawable { - public Action SidebarMetadataUpdated; - - [Resolved] - private IAPIProvider api { get; set; } + public Action RequestMorePosts; private FillFlowContainer content; private ShowMoreButton showMore; - private GetNewsRequest request; - private Cursor lastCursor; - - private readonly int? year; + private readonly GetNewsResponse initialResponse; /// /// 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) + /// Initial response to create articles from. + public ArticleListing(GetNewsResponse initialResponse) { - this.year = year; + this.initialResponse = initialResponse; } [BackgroundDependencyLoader] @@ -69,7 +61,8 @@ namespace osu.Game.Overlays.News.Displays RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) + Spacing = new Vector2(0, 10), + Children = initialResponse.NewsPosts.Select(p => new NewsCard(p)).ToList() }, showMore = new ShowMoreButton { @@ -79,37 +72,19 @@ namespace osu.Game.Overlays.News.Displays { Top = 15 }, - Action = performFetch, - Alpha = 0 + Action = RequestMorePosts, + Alpha = initialResponse.Cursor != null ? 1 : 0 } } }; - - performFetch(); - } - - private void performFetch() - { - request?.Cancel(); - - request = new GetNewsRequest(year, lastCursor); - request.Success += response => Schedule(() => onSuccess(response)); - api.PerformAsync(request); } private CancellationTokenSource cancellationToken; - private void onSuccess(GetNewsResponse response) + public void AddPosts(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; - LoadComponentsAsync(response.NewsPosts.Select(p => new NewsCard(p)).ToList(), loaded => { content.AddRange(loaded); @@ -121,7 +96,6 @@ namespace osu.Game.Overlays.News.Displays protected override void Dispose(bool isDisposing) { - request?.Cancel(); cancellationToken?.Cancel(); base.Dispose(isDisposing); } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index dd6de40ecb..751ac1d10a 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -6,6 +6,8 @@ using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Overlays.News; using osu.Game.Overlays.News.Displays; using osu.Game.Overlays.News.Sidebar; @@ -18,9 +20,12 @@ namespace osu.Game.Overlays private readonly Container sidebarContainer; private readonly NewsSidebar sidebar; - private readonly Container content; + private APIRequest lastRequest; + private Cursor lastCursor; + private int? year; + private CancellationTokenSource cancellationToken; private bool displayUpdateRequired = true; @@ -108,7 +113,11 @@ namespace osu.Game.Overlays protected void LoadDisplay(Drawable display) { ScrollFlow.ScrollToStart(); - LoadComponentAsync(display, loaded => content.Child = loaded, (cancellationToken = new CancellationTokenSource()).Token); + LoadComponentAsync(display, loaded => + { + content.Child = loaded; + Loading.Hide(); + }, (cancellationToken = new CancellationTokenSource()).Token); } protected override void UpdateAfterChildren() @@ -132,13 +141,41 @@ namespace osu.Game.Overlays Header.SetFrontPage(); - var page = new ArticleListing(year); - page.SidebarMetadataUpdated += metadata => Schedule(() => + this.year = year; + lastCursor = null; + + performListingRequest(response => { - sidebar.Metadata.Value = metadata; - Loading.Hide(); + sidebar.Metadata.Value = response.SidebarMetadata; + + var listing = new ArticleListing(response); + listing.RequestMorePosts += getMorePosts; + + LoadDisplay(listing); }); - LoadDisplay(page); + } + + private void getMorePosts() + { + lastRequest?.Cancel(); + performListingRequest(response => + { + if (content.Child is ArticleListing listing) + listing.AddPosts(response); + }); + } + + private void performListingRequest(Action onSuccess) + { + lastRequest = new GetNewsRequest(year, lastCursor); + + ((GetNewsRequest)lastRequest).Success += response => Schedule(() => + { + lastCursor = response.Cursor; + onSuccess?.Invoke(response); + }); + + API.PerformAsync(lastRequest); } private void loadArticle(string article) @@ -149,17 +186,18 @@ namespace osu.Game.Overlays // Temporary, should be handled by ArticleDisplay later LoadDisplay(Empty()); - Loading.Hide(); } private void beginLoading() { + lastRequest?.Cancel(); cancellationToken?.Cancel(); Loading.Show(); } protected override void Dispose(bool isDisposing) { + lastRequest?.Cancel(); cancellationToken?.Cancel(); base.Dispose(isDisposing); } From 83364285746b651bd8ccd27be11e0aaa127b493b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 15:10:33 +0900 Subject: [PATCH 310/429] 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 100e2d14a5084c6373da786d8370767ec686ce8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 15:14:55 +0900 Subject: [PATCH 311/429] Move call inside conditional --- osu.Game/Screens/Play/Player.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ee940fae40..a9f3edf049 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -519,10 +519,13 @@ namespace osu.Game.Screens.Play // there is a chance that the exit was performed after the transition to results has started. // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). - if (!this.IsCurrentScreen() && this.GetChildScreen() != null) + if (!this.IsCurrentScreen()) { ValidForResume = false; - this.MakeCurrent(); + + // in the potential case that this instance has already been exited, this is required to avoid a crash. + if (this.GetChildScreen() != null) + this.MakeCurrent(); return; } From e5f586f2a6940679a41b87ebe3f58d818226795e Mon Sep 17 00:00:00 2001 From: Firmatorenio Date: Mon, 24 May 2021 13:29:12 +0600 Subject: [PATCH 312/429] 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 313/429] 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 314/429] 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 315/429] 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 316/429] 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 7792efb154f271e528f4ed0df0ddf966123d5208 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 24 May 2021 12:28:06 +0300 Subject: [PATCH 317/429] Remove no longer used `BeatmapSkin` --- osu.Game/Skinning/BeatmapSkin.cs | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 osu.Game/Skinning/BeatmapSkin.cs diff --git a/osu.Game/Skinning/BeatmapSkin.cs b/osu.Game/Skinning/BeatmapSkin.cs deleted file mode 100644 index 14b845faeb..0000000000 --- a/osu.Game/Skinning/BeatmapSkin.cs +++ /dev/null @@ -1,32 +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.Audio.Sample; -using osu.Framework.Bindables; -using osu.Framework.Graphics.OpenGL.Textures; -using osu.Framework.Graphics.Textures; -using osu.Game.Audio; -using osu.Game.Beatmaps; - -namespace osu.Game.Skinning -{ - /// - /// An empty implementation of a beatmap skin, serves as a temporary default for s. - /// - /// - /// This should be removed once becomes instantiable or a new skin type for osu!lazer beatmaps is defined. - /// - public class BeatmapSkin : Skin - { - public BeatmapSkin(BeatmapInfo beatmap) - : base(BeatmapSkinExtensions.CreateSkinInfo(beatmap), null) - { - } - - public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; - - public override IBindable GetConfig(TLookup lookup) => null; - - public override ISample GetSample(ISampleInfo sampleInfo) => null; - } -} From 3585e2900ed8a488d6bb6fb69fddfd6305c37e32 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 24 May 2021 12:28:30 +0300 Subject: [PATCH 318/429] Replace unnecessary empty skin implementation with null --- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 2e4a9fa28f..bfce59c7de 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -3,13 +3,8 @@ using System.IO; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -42,23 +37,12 @@ namespace osu.Game.Tests.Beatmaps protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard(); - protected override ISkin GetSkin() => new EmptySkin(); + protected override ISkin GetSkin() => null; public override Stream GetStream(string storagePath) => null; protected override Texture GetBackground() => null; protected override Track GetBeatmapTrack() => null; - - private class EmptySkin : ISkin - { - public Drawable GetDrawableComponent(ISkinComponent component) => null; - - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; - - public ISample GetSample(ISampleInfo sampleInfo) => null; - - public IBindable GetConfig(TLookup lookup) => null; - } } } From 57640810b5023025b3b4ac933d35495663de39bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 17:23:09 +0900 Subject: [PATCH 319/429] 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 320/429] 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 321/429] 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 322/429] 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 ca1d1c58aba7888cf89bd42f8ced6539a1833d6a Mon Sep 17 00:00:00 2001 From: Swords Date: Mon, 24 May 2021 21:34:47 +1000 Subject: [PATCH 323/429] RestoreDefaultValueButton implements OsuButton --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 3 +- .../KeyBinding/RestorableKeyBindingRow.cs | 1 + .../Overlays/RestoreDefaultValueButton.cs | 44 +++++++------------ 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 959ba36c6a..8cc03160a2 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -131,9 +131,8 @@ namespace osu.Game.Overlays.KeyBinding IsDefault.BindValueChanged(isDefault => { - if (isDefault.NewValue && !computeIsDefaultValue()) + if (isDefault.NewValue) { - RestoreDefaults(); finalise(); } }); diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index b09c21378e..d07fffe6bc 100644 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -65,6 +65,7 @@ namespace osu.Game.Overlays.KeyBinding }, }; + restoreDefaultButton.Action = () => { KeyBindingRow.RestoreDefaults(); }; restoreDefaultButton.Current = KeyBindingRow.IsDefault; } } diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 51fb87da1d..d75657ea7a 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -6,17 +6,16 @@ using osu.Framework.Bindables; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays { - public class RestoreDefaultValueButton : Container, IHasTooltip, IHasCurrentValue + public class RestoreDefaultValueButton : OsuButton, IHasTooltip, IHasCurrentValue { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; @@ -34,34 +33,30 @@ namespace osu.Game.Overlays public RestoreDefaultValueButton() { + Height = 1; + RelativeSizeAxes = Axes.Y; Width = SettingsPanel.CONTENT_MARGINS; - Padding = new MarginPadding { Vertical = 1.5f }; - Alpha = 0f; } [BackgroundDependencyLoader] private void load(OsuColour colour) { + BackgroundColour = colour.Yellow; buttonColour = colour.Yellow; - - Child = new Container + Content.Width = 0.33f; + Content.CornerRadius = 3; + Content.EdgeEffect = new EdgeEffectParameters { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - CornerRadius = 3, - Masking = true, - Colour = buttonColour, - EdgeEffect = new EdgeEffectParameters - { - Colour = buttonColour.Opacity(0.1f), - Type = EdgeEffectType.Glow, - Radius = 2, - }, - Width = 0.33f, - Child = new Box { RelativeSizeAxes = Axes.Both }, + Colour = buttonColour.Opacity(0.1f), + Type = EdgeEffectType.Glow, + Radius = 2, }; + + Padding = new MarginPadding { Vertical = 1.5f }; + Alpha = 0f; + + Action += () => { if (!current.Disabled) current.SetDefault(); }; } protected override void LoadComplete() @@ -77,13 +72,6 @@ namespace osu.Game.Overlays public string TooltipText => "revert to default"; - protected override bool OnClick(ClickEvent e) - { - if (!current.Disabled) - current.SetDefault(); - return true; - } - protected override bool OnHover(HoverEvent e) { hovering = true; From 441e4e7d56fe35088bb2faef2ed741bb3ad82814 Mon Sep 17 00:00:00 2001 From: Swords Date: Mon, 24 May 2021 22:08:34 +1000 Subject: [PATCH 324/429] Formatting --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index d75657ea7a..0fe7b7322f 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -56,7 +56,10 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Vertical = 1.5f }; Alpha = 0f; - Action += () => { if (!current.Disabled) current.SetDefault(); }; + Action += () => + { + if (!current.Disabled) current.SetDefault(); + }; } protected override void LoadComplete() From 02cdd0b2deff3a03ee3a020d6e5a46684b5daa17 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 24 May 2021 19:10:37 +0700 Subject: [PATCH 325/429] 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 326/429] 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 518999ffab72b414a5d82bb0ba908d505d7f5187 Mon Sep 17 00:00:00 2001 From: Swords Date: Mon, 24 May 2021 22:49:40 +1000 Subject: [PATCH 327/429] Renaming files --- .../Settings/TestSceneKeyBindingPanel.cs | 42 +- .../Overlays/KeyBinding/BasicKeyBindingRow.cs | 463 +++++++++++++++++ osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 465 ++---------------- .../KeyBinding/KeyBindingsSubsection.cs | 4 +- .../KeyBinding/RestorableKeyBindingRow.cs | 72 --- 5 files changed, 514 insertions(+), 532 deletions(-) create mode 100644 osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs delete mode 100644 osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 3edba2ddd7..face2a498d 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClickTwiceOnClearButton() { - KeyBindingRow firstRow = null; + BasicKeyBindingRow firstRow = null; AddStep("click first row", () => { - firstRow = panel.ChildrenOfType().First(); + firstRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(firstRow); InputManager.Click(MouseButton.Left); @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("schedule button clicks", () => { - var clearButton = firstRow.ChildrenOfType().Single(); + var clearButton = firstRow.ChildrenOfType().Single(); InputManager.MoveMouseTo(clearButton); @@ -68,22 +68,22 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClearButtonOnBindings() { - KeyBindingRow multiBindingRow = null; + BasicKeyBindingRow multiBindingRow = null; AddStep("click first row with two bindings", () => { - multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); clickClearButton(); - AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text.ToString())); + AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text.ToString())); AddStep("click second binding", () => { - var target = multiBindingRow.ChildrenOfType().ElementAt(1); + var target = multiBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -91,13 +91,13 @@ namespace osu.Game.Tests.Visual.Settings clickClearButton(); - AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text.ToString())); + AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text.ToString())); void clickClearButton() { AddStep("click clear button", () => { - var clearButton = multiBindingRow.ChildrenOfType().Single(); + var clearButton = multiBindingRow.ChildrenOfType().Single(); InputManager.MoveMouseTo(clearButton); InputManager.Click(MouseButton.Left); @@ -108,11 +108,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestSingleBindingResetButton() { - RestorableKeyBindingRow settingsKeyBindingRow = null; + KeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); @@ -131,17 +131,17 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); } [Test] public void TestResetAllBindingsButton() { - RestorableKeyBindingRow settingsKeyBindingRow = null; + KeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); @@ -160,26 +160,26 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); } [Test] public void TestClickRowSelectsFirstBinding() { - KeyBindingRow multiBindingRow = null; + BasicKeyBindingRow multiBindingRow = null; AddStep("click first row with two bindings", () => { - multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); + AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); AddStep("click second binding", () => { - var target = multiBindingRow.ChildrenOfType().ElementAt(1); + var target = multiBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -187,12 +187,12 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click back binding row", () => { - multiBindingRow = panel.ChildrenOfType().ElementAt(10); + multiBindingRow = panel.ChildrenOfType().ElementAt(10); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); + AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); } } } \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs new file mode 100644 index 0000000000..acdf273622 --- /dev/null +++ b/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs @@ -0,0 +1,463 @@ +// 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; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Overlays.KeyBinding +{ + public class BasicKeyBindingRow : Container + { + private readonly object action; + private readonly IEnumerable bindings; + + private const float transition_time = 150; + + private const float height = 20; + + private const float padding = 5; + + private FillFlowContainer cancelAndClearButtons; + private FillFlowContainer buttons; + + public Bindable IsDefault { get; } = new BindableBool(true) + { + Default = true + }; + + public BasicKeyBindingRow(object action, IEnumerable bindings) + { + this.action = action; + this.bindings = bindings; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = padding; + } + + [Resolved] + private KeyBindingStore store { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + updateIsDefaultValue(); + + EdgeEffect = new EdgeEffectParameters + { + Radius = 2, + Colour = colours.YellowDark.Opacity(0), + Type = EdgeEffectType.Shadow, + Hollow = true, + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + new OsuSpriteText + { + Text = action.GetDescription(), + Margin = new MarginPadding(padding), + }, + buttons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }, + cancelAndClearButtons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(padding) { Top = height + padding * 2 }, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Alpha = 0, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new CancelButton { Action = finalise }, + new ClearButton { Action = clear }, + }, + } + }; + foreach (var b in bindings) + buttons.Add(new KeyButton(b)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsDefault.BindValueChanged(isDefault => + { + if (isDefault.NewValue) + { + finalise(); + } + }); + } + + public void RestoreDefaults() + { + int i = 0; + + foreach (var d in Defaults) + { + var button = buttons[i++]; + button.UpdateKeyCombination(d); + store.Update(button.KeyBinding); + } + + updateIsDefaultValue(); + } + + protected override bool OnHover(HoverEvent e) + { + FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); + + base.OnHoverLost(e); + } + + public override bool AcceptsFocus => bindTarget == null; + + private KeyButton bindTarget; + + public bool AllowMainMouseButtons; + + public IEnumerable Defaults; + + private bool isModifier(Key k) => k < Key.F1; + + protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (!HasFocus || !bindTarget.IsHovered) + return base.OnMouseDown(e); + + if (!AllowMainMouseButtons) + { + switch (e.Button) + { + case MouseButton.Left: + case MouseButton.Right: + return true; + } + } + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + // don't do anything until the last button is released. + if (!HasFocus || e.HasAnyButtonPressed) + { + base.OnMouseUp(e); + return; + } + + if (bindTarget.IsHovered) + finalise(); + // prevent updating bind target before clear button's action + else if (!cancelAndClearButtons.Any(b => b.IsHovered)) + updateBindTarget(); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (HasFocus) + { + if (bindTarget.IsHovered) + { + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta)); + finalise(); + return true; + } + } + + return base.OnScroll(e); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + if (!isModifier(e.Key)) finalise(); + + return true; + } + + protected override void OnKeyUp(KeyUpEvent e) + { + if (!HasFocus) + { + base.OnKeyUp(e); + return; + } + + finalise(); + } + + protected override bool OnJoystickPress(JoystickPressEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + finalise(); + + return true; + } + + protected override void OnJoystickRelease(JoystickReleaseEvent e) + { + if (!HasFocus) + { + base.OnJoystickRelease(e); + return; + } + + finalise(); + } + + protected override bool OnMidiDown(MidiDownEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + finalise(); + + return true; + } + + protected override void OnMidiUp(MidiUpEvent e) + { + if (!HasFocus) + { + base.OnMidiUp(e); + return; + } + + finalise(); + } + + private void clear() + { + if (bindTarget == null) + return; + + bindTarget.UpdateKeyCombination(InputKey.None); + finalise(); + } + + private void finalise() + { + if (bindTarget != null) + { + store.Update(bindTarget.KeyBinding); + + updateIsDefaultValue(); + + bindTarget.IsBinding = false; + Schedule(() => + { + // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) + bindTarget = null; + }); + } + + if (HasFocus) + GetContainingInputManager().ChangeFocus(null); + + cancelAndClearButtons.FadeOut(300, Easing.OutQuint); + cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; + } + + private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); + + private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); + + protected override void OnFocus(FocusEvent e) + { + AutoSizeDuration = 500; + AutoSizeEasing = Easing.OutQuint; + + cancelAndClearButtons.FadeIn(300, Easing.OutQuint); + cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y; + + updateBindTarget(); + base.OnFocus(e); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + finalise(); + base.OnFocusLost(e); + } + + /// + /// Updates the bind target to the currently hovered key button or the first if clicked anywhere else. + /// + private void updateBindTarget() + { + if (bindTarget != null) bindTarget.IsBinding = false; + bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); + if (bindTarget != null) bindTarget.IsBinding = true; + } + + private class CancelButton : TriangleButton + { + public CancelButton() + { + Text = "Cancel"; + Size = new Vector2(80, 20); + } + } + + public class ClearButton : DangerousTriangleButton + { + public ClearButton() + { + Text = "Clear"; + Size = new Vector2(80, 20); + } + } + + public class KeyButton : Container + { + public readonly Framework.Input.Bindings.KeyBinding KeyBinding; + + private readonly Box box; + public readonly OsuSpriteText Text; + + private Color4 hoverColour; + + private bool isBinding; + + public bool IsBinding + { + get => isBinding; + set + { + if (value == isBinding) return; + + isBinding = value; + + updateHoverState(); + } + } + + public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) + { + KeyBinding = keyBinding; + + Margin = new MarginPadding(padding); + + // todo: use this in a meaningful way + // var isDefault = keyBinding.Action is Enum; + + Masking = true; + CornerRadius = padding; + + Height = height; + AutoSizeAxes = Axes.X; + + Children = new Drawable[] + { + new Container + { + AlwaysPresent = true, + Width = 80, + Height = height, + }, + box = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + Text = new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 10), + Margin = new MarginPadding(5), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = keyBinding.KeyCombination.ReadableString(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.YellowDark; + } + + protected override bool OnHover(HoverEvent e) + { + updateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateHoverState(); + base.OnHoverLost(e); + } + + private void updateHoverState() + { + if (isBinding) + { + box.FadeColour(Color4.White, transition_time, Easing.OutQuint); + Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint); + } + else + { + box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint); + Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint); + } + } + + public void UpdateKeyCombination(KeyCombination newCombination) + { + KeyBinding.KeyCombination = newCombination; + Text.Text = KeyBinding.KeyCombination.ReadableString(); + } + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 8cc03160a2..3221b66bce 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -1,38 +1,20 @@ -// 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 System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Input; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; +using osu.Game.Rulesets; namespace osu.Game.Overlays.KeyBinding { public class KeyBindingRow : Container, IFilterable { - private readonly object action; - private readonly IEnumerable bindings; - - private const float transition_time = 150; - - private const float height = 20; - - private const float padding = 5; + private readonly object key; + private readonly ICollection bindings; + public readonly BasicKeyBindingRow BasicKeyBindingRow; private bool matchingFilter; @@ -48,434 +30,43 @@ namespace osu.Game.Overlays.KeyBinding public bool FilteringActive { get; set; } - private OsuSpriteText text; - private FillFlowContainer cancelAndClearButtons; - private FillFlowContainer buttons; + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - public Bindable IsDefault { get; } = new BindableBool(true) + public KeyBindingRow( + object key, + ICollection bindings, + RulesetInfo ruleset, + IEnumerable defaults) { - Default = true - }; - - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString()); - - public KeyBindingRow(object action, IEnumerable bindings) - { - this.action = action; + this.key = key; this.bindings = bindings; + + RestoreDefaultValueButton restoreDefaultButton; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; - Masking = true; - CornerRadius = padding; - } - - [Resolved] - private KeyBindingStore store { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - updateIsDefaultValue(); - - EdgeEffect = new EdgeEffectParameters + BasicKeyBindingRow = new BasicKeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) { - Radius = 2, - Colour = colours.YellowDark.Opacity(0), - Type = EdgeEffectType.Shadow, - Hollow = true, + AllowMainMouseButtons = ruleset != null, + Defaults = defaults }; - Children = new Drawable[] + InternalChildren = new Drawable[] { - new Box + restoreDefaultButton = new RestoreDefaultValueButton(), + new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Child = BasicKeyBindingRow }, - text = new OsuSpriteText - { - Text = action.GetDescription(), - Margin = new MarginPadding(padding), - }, - buttons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }, - cancelAndClearButtons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding(padding) { Top = height + padding * 2 }, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Alpha = 0, - Spacing = new Vector2(5), - Children = new Drawable[] - { - new CancelButton { Action = finalise }, - new ClearButton { Action = clear }, - }, - } }; - foreach (var b in bindings) - buttons.Add(new KeyButton(b)); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - IsDefault.BindValueChanged(isDefault => - { - if (isDefault.NewValue) - { - finalise(); - } - }); - } - - public void RestoreDefaults() - { - int i = 0; - - foreach (var d in Defaults) - { - var button = buttons[i++]; - button.UpdateKeyCombination(d); - store.Update(button.KeyBinding); - } - - updateIsDefaultValue(); - } - - protected override bool OnHover(HoverEvent e) - { - FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); - - base.OnHoverLost(e); - } - - public override bool AcceptsFocus => bindTarget == null; - - private KeyButton bindTarget; - - public bool AllowMainMouseButtons; - - public IEnumerable Defaults; - - private bool isModifier(Key k) => k < Key.F1; - - protected override bool OnClick(ClickEvent e) => true; - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (!HasFocus || !bindTarget.IsHovered) - return base.OnMouseDown(e); - - if (!AllowMainMouseButtons) - { - switch (e.Button) - { - case MouseButton.Left: - case MouseButton.Right: - return true; - } - } - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - return true; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - // don't do anything until the last button is released. - if (!HasFocus || e.HasAnyButtonPressed) - { - base.OnMouseUp(e); - return; - } - - if (bindTarget.IsHovered) - finalise(); - // prevent updating bind target before clear button's action - else if (!cancelAndClearButtons.Any(b => b.IsHovered)) - updateBindTarget(); - } - - protected override bool OnScroll(ScrollEvent e) - { - if (HasFocus) - { - if (bindTarget.IsHovered) - { - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta)); - finalise(); - return true; - } - } - - return base.OnScroll(e); - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (!HasFocus) - return false; - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - if (!isModifier(e.Key)) finalise(); - - return true; - } - - protected override void OnKeyUp(KeyUpEvent e) - { - if (!HasFocus) - { - base.OnKeyUp(e); - return; - } - - finalise(); - } - - protected override bool OnJoystickPress(JoystickPressEvent e) - { - if (!HasFocus) - return false; - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - finalise(); - - return true; - } - - protected override void OnJoystickRelease(JoystickReleaseEvent e) - { - if (!HasFocus) - { - base.OnJoystickRelease(e); - return; - } - - finalise(); - } - - protected override bool OnMidiDown(MidiDownEvent e) - { - if (!HasFocus) - return false; - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - finalise(); - - return true; - } - - protected override void OnMidiUp(MidiUpEvent e) - { - if (!HasFocus) - { - base.OnMidiUp(e); - return; - } - - finalise(); - } - - private void clear() - { - if (bindTarget == null) - return; - - bindTarget.UpdateKeyCombination(InputKey.None); - finalise(); - } - - private void finalise() - { - if (bindTarget != null) - { - store.Update(bindTarget.KeyBinding); - - updateIsDefaultValue(); - - bindTarget.IsBinding = false; - Schedule(() => - { - // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) - bindTarget = null; - }); - } - - if (HasFocus) - GetContainingInputManager().ChangeFocus(null); - - cancelAndClearButtons.FadeOut(300, Easing.OutQuint); - cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; - } - - private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); - - private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); - - protected override void OnFocus(FocusEvent e) - { - AutoSizeDuration = 500; - AutoSizeEasing = Easing.OutQuint; - - cancelAndClearButtons.FadeIn(300, Easing.OutQuint); - cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y; - - updateBindTarget(); - base.OnFocus(e); - } - - protected override void OnFocusLost(FocusLostEvent e) - { - finalise(); - base.OnFocusLost(e); - } - - /// - /// Updates the bind target to the currently hovered key button or the first if clicked anywhere else. - /// - private void updateBindTarget() - { - if (bindTarget != null) bindTarget.IsBinding = false; - bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); - if (bindTarget != null) bindTarget.IsBinding = true; - } - - private class CancelButton : TriangleButton - { - public CancelButton() - { - Text = "Cancel"; - Size = new Vector2(80, 20); - } - } - - public class ClearButton : DangerousTriangleButton - { - public ClearButton() - { - Text = "Clear"; - Size = new Vector2(80, 20); - } - } - - public class KeyButton : Container - { - public readonly Framework.Input.Bindings.KeyBinding KeyBinding; - - private readonly Box box; - public readonly OsuSpriteText Text; - - private Color4 hoverColour; - - private bool isBinding; - - public bool IsBinding - { - get => isBinding; - set - { - if (value == isBinding) return; - - isBinding = value; - - updateHoverState(); - } - } - - public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) - { - KeyBinding = keyBinding; - - Margin = new MarginPadding(padding); - - // todo: use this in a meaningful way - // var isDefault = keyBinding.Action is Enum; - - Masking = true; - CornerRadius = padding; - - Height = height; - AutoSizeAxes = Axes.X; - - Children = new Drawable[] - { - new Container - { - AlwaysPresent = true, - Width = 80, - Height = height, - }, - box = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - Text = new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 10), - Margin = new MarginPadding(5), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = keyBinding.KeyCombination.ReadableString(), - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.YellowDark; - } - - protected override bool OnHover(HoverEvent e) - { - updateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateHoverState(); - base.OnHoverLost(e); - } - - private void updateHoverState() - { - if (isBinding) - { - box.FadeColour(Color4.White, transition_time, Easing.OutQuint); - Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint); - } - else - { - box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint); - Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint); - } - } - - public void UpdateKeyCombination(KeyCombination newCombination) - { - KeyBinding.KeyCombination = newCombination; - Text.Text = KeyBinding.KeyCombination.ReadableString(); - } + restoreDefaultButton.Action = () => { BasicKeyBindingRow.RestoreDefaults(); }; + restoreDefaultButton.Current = BasicKeyBindingRow.IsDefault; } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 737c640b5a..b1a5895449 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -38,12 +38,12 @@ namespace osu.Game.Overlays.KeyBinding foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { // one row per valid action. - Add(new RestorableKeyBindingRow(defaultGroup.Key, bindings, Ruleset, defaultGroup.Select(d => d.KeyCombination))); + Add(new KeyBindingRow(defaultGroup.Key, bindings, Ruleset, defaultGroup.Select(d => d.KeyCombination))); } Add(new ResetButton { - Action = () => Children.OfType().ForEach(k => k.KeyBindingRow.RestoreDefaults()) + Action = () => Children.OfType().ForEach(k => k.BasicKeyBindingRow.RestoreDefaults()) }); } } diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs deleted file mode 100644 index d07fffe6bc..0000000000 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ /dev/null @@ -1,72 +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.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets; - -namespace osu.Game.Overlays.KeyBinding -{ - public class RestorableKeyBindingRow : Container, IFilterable - { - private readonly object key; - private readonly ICollection bindings; - public readonly KeyBindingRow KeyBindingRow; - - private bool matchingFilter; - - public bool MatchingFilter - { - get => matchingFilter; - set - { - matchingFilter = value; - this.FadeTo(!matchingFilter ? 0 : 1); - } - } - - public bool FilteringActive { get; set; } - - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - - public RestorableKeyBindingRow( - object key, - ICollection bindings, - RulesetInfo ruleset, - IEnumerable defaults) - { - this.key = key; - this.bindings = bindings; - - RestoreDefaultValueButton restoreDefaultButton; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; - - KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) - { - AllowMainMouseButtons = ruleset != null, - Defaults = defaults - }; - - InternalChildren = new Drawable[] - { - restoreDefaultButton = new RestoreDefaultValueButton(), - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = KeyBindingRow - }, - }; - - restoreDefaultButton.Action = () => { KeyBindingRow.RestoreDefaults(); }; - restoreDefaultButton.Current = KeyBindingRow.IsDefault; - } - } -} From 6f155fbd08500f58ce3629a0b958b8d549546114 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 May 2021 21:54:55 +0900 Subject: [PATCH 328/429] 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 329/429] 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 330/429] 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 331/429] 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 332/429] 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 333/429] 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 9de07de46784c479fd32f6867eee3692f911693d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 25 May 2021 12:14:07 +0700 Subject: [PATCH 334/429] move text flow container inside markdown container --- .../Overlays/Wiki/Markdown/WikiMarkdownContainer.cs | 6 ++++++ .../Wiki/Markdown/WikiMarkdownTextFlowContainer.cs | 13 ------------- 2 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 81115293d4..d4ad0bee4d 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -3,6 +3,7 @@ using Markdig.Extensions.Yaml; using Markdig.Syntax; +using Markdig.Syntax.Inlines; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics.Containers.Markdown; @@ -35,5 +36,10 @@ namespace osu.Game.Overlays.Wiki.Markdown protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) => new WikiMarkdownParagraph(paragraphBlock); protected virtual FillFlowContainer CreateNotice(YamlFrontMatterBlock yamlFrontMatterBlock) => new WikiNoticeContainer(yamlFrontMatterBlock); + + private class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer + { + protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline)); + } } } diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs deleted file mode 100644 index 1c2b37a219..0000000000 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownTextFlowContainer.cs +++ /dev/null @@ -1,13 +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 Markdig.Syntax.Inlines; -using osu.Game.Graphics.Containers.Markdown; - -namespace osu.Game.Overlays.Wiki.Markdown -{ - public class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer - { - protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline)); - } -} From e3507d545391b6cbf665de5e58fdd485e7e48ab5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 16:06:39 +0900 Subject: [PATCH 335/429] Move `DrawableStoryboard`'s aspect application to inside its own class --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 3 +++ osu.Game/Storyboards/Storyboard.cs | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 4c42823779..1cd9b40089 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -58,6 +58,9 @@ namespace osu.Game.Storyboards.Drawables { Storyboard = storyboard; Size = new Vector2(640, 480); + + Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); + Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index bc61f704dd..08e80bc48c 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -85,12 +85,8 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) - { - var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); - return drawable; - } + public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) => + new DrawableStoryboard(this); public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) { From 0c55bba220ec1f95bdfaf27cd105c0a72eafb6a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 16:07:17 +0900 Subject: [PATCH 336/429] Allow storyboards to be widescreen if only a video element exists This matches stable behaviour, which will allow videos to display filling the screen if they are the only thing contained within the "storyboard". --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 1cd9b40089..bf67194e84 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -57,9 +57,12 @@ namespace osu.Game.Storyboards.Drawables public DrawableStoryboard(Storyboard storyboard) { Storyboard = storyboard; + Size = new Vector2(640, 480); - Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); + bool onlyHasVideoElements = !Storyboard.Layers.Any(l => l.Elements.Any(e => !(e is StoryboardVideo))); + + Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f); Anchor = Anchor.Centre; Origin = Anchor.Centre; From 5ea948aabe67b7ec36eddb884c158d4540141051 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 16:17:28 +0900 Subject: [PATCH 337/429] Bypass 640x480 coordinate space for video storyboard elements This allows the `FillMode.Fill` to take up the full space of the storyboard container. --- .../Drawables/DrawableStoryboardLayer.cs | 11 ++++--- osu.Game/Storyboards/Storyboard.cs | 2 +- osu.Game/Storyboards/StoryboardLayer.cs | 2 +- osu.Game/Storyboards/StoryboardLayerVideo.cs | 32 +++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Storyboards/StoryboardLayerVideo.cs diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 2ada83c3b4..1085b52d65 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -5,6 +5,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -15,6 +16,8 @@ namespace osu.Game.Storyboards.Drawables public override bool IsPresent => Enabled && base.IsPresent; + protected LayerElementContainer ElementContainer { get; } + public DrawableStoryboardLayer(StoryboardLayer layer) { Layer = layer; @@ -24,10 +27,10 @@ namespace osu.Game.Storyboards.Drawables Enabled = layer.VisibleWhenPassing; Masking = layer.Masking; - InternalChild = new LayerElementContainer(layer); + InternalChild = ElementContainer = new LayerElementContainer(layer); } - private class LayerElementContainer : LifetimeManagementContainer + protected class LayerElementContainer : LifetimeManagementContainer { private readonly StoryboardLayer storyboardLayer; @@ -35,8 +38,8 @@ namespace osu.Game.Storyboards.Drawables { storyboardLayer = layer; - Width = 640; - Height = 480; + Size = new Vector2(640, 480); + Anchor = Anchor.Centre; Origin = Anchor.Centre; } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 08e80bc48c..06be6c2d20 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -53,7 +53,7 @@ namespace osu.Game.Storyboards public Storyboard() { - layers.Add("Video", new StoryboardLayer("Video", 4, false)); + layers.Add("Video", new StoryboardLayerVideo("Video", 4, false)); layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, }); diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index 1cde7cf67a..fa9d4ebfea 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Storyboards Elements.Add(element); } - public DrawableStoryboardLayer CreateDrawable() + public virtual DrawableStoryboardLayer CreateDrawable() => new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name }; } } diff --git a/osu.Game/Storyboards/StoryboardLayerVideo.cs b/osu.Game/Storyboards/StoryboardLayerVideo.cs new file mode 100644 index 0000000000..7235df7a41 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardLayerVideo.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; +using osu.Game.Storyboards.Drawables; +using osuTK; + +namespace osu.Game.Storyboards +{ + public class StoryboardLayerVideo : StoryboardLayer + { + public StoryboardLayerVideo(string name, int depth, bool masking) + : base(name, depth, masking) + { + } + + public override DrawableStoryboardLayer CreateDrawable() + => new DrawableStoryboardLayerVideo(this) { Depth = Depth, Name = Name }; + + public class DrawableStoryboardLayerVideo : DrawableStoryboardLayer + { + public DrawableStoryboardLayerVideo(StoryboardLayerVideo layer) + : base(layer) + { + // for videos we want to take on the full size of the storyboard container hierarchy + // to allow the video to fill the full available region. + ElementContainer.RelativeSizeAxes = Axes.Both; + ElementContainer.Size = Vector2.One; + } + } + } +} From 871ca8054f4cfe36567fe653f45f9a333c5018bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:50:33 +0900 Subject: [PATCH 338/429] Rename classes as per review feedback --- osu.Game/Storyboards/Storyboard.cs | 2 +- ...StoryboardLayerVideo.cs => StoryboardVideoLayer.cs} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Storyboards/{StoryboardLayerVideo.cs => StoryboardVideoLayer.cs} (71%) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 06be6c2d20..3486c1d66a 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -53,7 +53,7 @@ namespace osu.Game.Storyboards public Storyboard() { - layers.Add("Video", new StoryboardLayerVideo("Video", 4, false)); + layers.Add("Video", new StoryboardVideoLayer("Video", 4, false)); layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, }); diff --git a/osu.Game/Storyboards/StoryboardLayerVideo.cs b/osu.Game/Storyboards/StoryboardVideoLayer.cs similarity index 71% rename from osu.Game/Storyboards/StoryboardLayerVideo.cs rename to osu.Game/Storyboards/StoryboardVideoLayer.cs index 7235df7a41..2a01c2274a 100644 --- a/osu.Game/Storyboards/StoryboardLayerVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideoLayer.cs @@ -7,19 +7,19 @@ using osuTK; namespace osu.Game.Storyboards { - public class StoryboardLayerVideo : StoryboardLayer + public class StoryboardVideoLayer : StoryboardLayer { - public StoryboardLayerVideo(string name, int depth, bool masking) + public StoryboardVideoLayer(string name, int depth, bool masking) : base(name, depth, masking) { } public override DrawableStoryboardLayer CreateDrawable() - => new DrawableStoryboardLayerVideo(this) { Depth = Depth, Name = Name }; + => new DrawableStoryboardVideoLayer(this) { Depth = Depth, Name = Name }; - public class DrawableStoryboardLayerVideo : DrawableStoryboardLayer + public class DrawableStoryboardVideoLayer : DrawableStoryboardLayer { - public DrawableStoryboardLayerVideo(StoryboardLayerVideo layer) + public DrawableStoryboardVideoLayer(StoryboardVideoLayer layer) : base(layer) { // for videos we want to take on the full size of the storyboard container hierarchy From 342acadae2381c0822c596f5f3c71efcdc9afef5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:51:49 +0900 Subject: [PATCH 339/429] Change LINQ query for better readability --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index bf67194e84..ca041da801 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -60,7 +60,7 @@ namespace osu.Game.Storyboards.Drawables Size = new Vector2(640, 480); - bool onlyHasVideoElements = !Storyboard.Layers.Any(l => l.Elements.Any(e => !(e is StoryboardVideo))); + bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).Any(e => !(e is StoryboardVideo)); Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f); From ce845a9f8d5748c0ef1ca35edcde6e5469abb076 Mon Sep 17 00:00:00 2001 From: Swords Date: Tue, 25 May 2021 21:00:38 +1000 Subject: [PATCH 340/429] Apply the rest of requested changes --- .../Overlays/KeyBinding/BasicKeyBindingRow.cs | 5 +--- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 30 +++++++------------ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs index acdf273622..91d9aa70bd 100644 --- a/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs @@ -37,10 +37,7 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - public Bindable IsDefault { get; } = new BindableBool(true) - { - Default = true - }; + public Bindable IsDefault { get; } = new BindableBool(true); public BasicKeyBindingRow(object action, IEnumerable bindings) { diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 3221b66bce..f799b4810f 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -32,41 +32,33 @@ namespace osu.Game.Overlays.KeyBinding public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - public KeyBindingRow( - object key, - ICollection bindings, - RulesetInfo ruleset, - IEnumerable defaults) - { + public KeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) { this.key = key; this.bindings = bindings; - RestoreDefaultValueButton restoreDefaultButton; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; - BasicKeyBindingRow = new BasicKeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) - { - AllowMainMouseButtons = ruleset != null, - Defaults = defaults - }; - InternalChildren = new Drawable[] { - restoreDefaultButton = new RestoreDefaultValueButton(), + new RestoreDefaultValueButton() + { + Current = BasicKeyBindingRow.IsDefault, + Action = () => { BasicKeyBindingRow.RestoreDefaults(); } + }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = BasicKeyBindingRow + Child = BasicKeyBindingRow = new BasicKeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) + { + AllowMainMouseButtons = ruleset != null, + Defaults = defaults + } }, }; - - restoreDefaultButton.Action = () => { BasicKeyBindingRow.RestoreDefaults(); }; - restoreDefaultButton.Current = BasicKeyBindingRow.IsDefault; } } } From d9f5b578bf6f81fad7037480cf8ce95ac47eadad Mon Sep 17 00:00:00 2001 From: Swords Date: Tue, 25 May 2021 21:08:40 +1000 Subject: [PATCH 341/429] Restore class names --- .../Settings/TestSceneKeyBindingPanel.cs | 42 +- .../Overlays/KeyBinding/BasicKeyBindingRow.cs | 460 ----------------- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 474 ++++++++++++++++-- .../KeyBinding/KeyBindingsSubsection.cs | 4 +- .../KeyBinding/RestorableKeyBindingRow.cs | 64 +++ 5 files changed, 522 insertions(+), 522 deletions(-) delete mode 100644 osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs create mode 100644 osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index face2a498d..bd3e94dffa 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClickTwiceOnClearButton() { - BasicKeyBindingRow firstRow = null; + KeyBindingRow firstRow = null; AddStep("click first row", () => { - firstRow = panel.ChildrenOfType().First(); + firstRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(firstRow); InputManager.Click(MouseButton.Left); @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("schedule button clicks", () => { - var clearButton = firstRow.ChildrenOfType().Single(); + var clearButton = firstRow.ChildrenOfType().Single(); InputManager.MoveMouseTo(clearButton); @@ -68,22 +68,22 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClearButtonOnBindings() { - BasicKeyBindingRow multiBindingRow = null; + KeyBindingRow multiBindingRow = null; AddStep("click first row with two bindings", () => { - multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); clickClearButton(); - AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text.ToString())); + AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text.ToString())); AddStep("click second binding", () => { - var target = multiBindingRow.ChildrenOfType().ElementAt(1); + var target = multiBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -91,13 +91,13 @@ namespace osu.Game.Tests.Visual.Settings clickClearButton(); - AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text.ToString())); + AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text.ToString())); void clickClearButton() { AddStep("click clear button", () => { - var clearButton = multiBindingRow.ChildrenOfType().Single(); + var clearButton = multiBindingRow.ChildrenOfType().Single(); InputManager.MoveMouseTo(clearButton); InputManager.Click(MouseButton.Left); @@ -108,11 +108,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestSingleBindingResetButton() { - KeyBindingRow settingsKeyBindingRow = null; + RestorableKeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); @@ -131,17 +131,17 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); } [Test] public void TestResetAllBindingsButton() { - KeyBindingRow settingsKeyBindingRow = null; + RestorableKeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); @@ -160,26 +160,26 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); } [Test] public void TestClickRowSelectsFirstBinding() { - BasicKeyBindingRow multiBindingRow = null; + KeyBindingRow multiBindingRow = null; AddStep("click first row with two bindings", () => { - multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); + AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); AddStep("click second binding", () => { - var target = multiBindingRow.ChildrenOfType().ElementAt(1); + var target = multiBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -187,12 +187,12 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click back binding row", () => { - multiBindingRow = panel.ChildrenOfType().ElementAt(10); + multiBindingRow = panel.ChildrenOfType().ElementAt(10); InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); + AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); } } } \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs deleted file mode 100644 index 91d9aa70bd..0000000000 --- a/osu.Game/Overlays/KeyBinding/BasicKeyBindingRow.cs +++ /dev/null @@ -1,460 +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.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Input; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Overlays.KeyBinding -{ - public class BasicKeyBindingRow : Container - { - private readonly object action; - private readonly IEnumerable bindings; - - private const float transition_time = 150; - - private const float height = 20; - - private const float padding = 5; - - private FillFlowContainer cancelAndClearButtons; - private FillFlowContainer buttons; - - public Bindable IsDefault { get; } = new BindableBool(true); - - public BasicKeyBindingRow(object action, IEnumerable bindings) - { - this.action = action; - this.bindings = bindings; - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Masking = true; - CornerRadius = padding; - } - - [Resolved] - private KeyBindingStore store { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - updateIsDefaultValue(); - - EdgeEffect = new EdgeEffectParameters - { - Radius = 2, - Colour = colours.YellowDark.Opacity(0), - Type = EdgeEffectType.Shadow, - Hollow = true, - }; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - new OsuSpriteText - { - Text = action.GetDescription(), - Margin = new MarginPadding(padding), - }, - buttons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }, - cancelAndClearButtons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding(padding) { Top = height + padding * 2 }, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Alpha = 0, - Spacing = new Vector2(5), - Children = new Drawable[] - { - new CancelButton { Action = finalise }, - new ClearButton { Action = clear }, - }, - } - }; - foreach (var b in bindings) - buttons.Add(new KeyButton(b)); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - IsDefault.BindValueChanged(isDefault => - { - if (isDefault.NewValue) - { - finalise(); - } - }); - } - - public void RestoreDefaults() - { - int i = 0; - - foreach (var d in Defaults) - { - var button = buttons[i++]; - button.UpdateKeyCombination(d); - store.Update(button.KeyBinding); - } - - updateIsDefaultValue(); - } - - protected override bool OnHover(HoverEvent e) - { - FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); - - base.OnHoverLost(e); - } - - public override bool AcceptsFocus => bindTarget == null; - - private KeyButton bindTarget; - - public bool AllowMainMouseButtons; - - public IEnumerable Defaults; - - private bool isModifier(Key k) => k < Key.F1; - - protected override bool OnClick(ClickEvent e) => true; - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (!HasFocus || !bindTarget.IsHovered) - return base.OnMouseDown(e); - - if (!AllowMainMouseButtons) - { - switch (e.Button) - { - case MouseButton.Left: - case MouseButton.Right: - return true; - } - } - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - return true; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - // don't do anything until the last button is released. - if (!HasFocus || e.HasAnyButtonPressed) - { - base.OnMouseUp(e); - return; - } - - if (bindTarget.IsHovered) - finalise(); - // prevent updating bind target before clear button's action - else if (!cancelAndClearButtons.Any(b => b.IsHovered)) - updateBindTarget(); - } - - protected override bool OnScroll(ScrollEvent e) - { - if (HasFocus) - { - if (bindTarget.IsHovered) - { - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta)); - finalise(); - return true; - } - } - - return base.OnScroll(e); - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (!HasFocus) - return false; - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - if (!isModifier(e.Key)) finalise(); - - return true; - } - - protected override void OnKeyUp(KeyUpEvent e) - { - if (!HasFocus) - { - base.OnKeyUp(e); - return; - } - - finalise(); - } - - protected override bool OnJoystickPress(JoystickPressEvent e) - { - if (!HasFocus) - return false; - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - finalise(); - - return true; - } - - protected override void OnJoystickRelease(JoystickReleaseEvent e) - { - if (!HasFocus) - { - base.OnJoystickRelease(e); - return; - } - - finalise(); - } - - protected override bool OnMidiDown(MidiDownEvent e) - { - if (!HasFocus) - return false; - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); - finalise(); - - return true; - } - - protected override void OnMidiUp(MidiUpEvent e) - { - if (!HasFocus) - { - base.OnMidiUp(e); - return; - } - - finalise(); - } - - private void clear() - { - if (bindTarget == null) - return; - - bindTarget.UpdateKeyCombination(InputKey.None); - finalise(); - } - - private void finalise() - { - if (bindTarget != null) - { - store.Update(bindTarget.KeyBinding); - - updateIsDefaultValue(); - - bindTarget.IsBinding = false; - Schedule(() => - { - // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) - bindTarget = null; - }); - } - - if (HasFocus) - GetContainingInputManager().ChangeFocus(null); - - cancelAndClearButtons.FadeOut(300, Easing.OutQuint); - cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; - } - - private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); - - private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); - - protected override void OnFocus(FocusEvent e) - { - AutoSizeDuration = 500; - AutoSizeEasing = Easing.OutQuint; - - cancelAndClearButtons.FadeIn(300, Easing.OutQuint); - cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y; - - updateBindTarget(); - base.OnFocus(e); - } - - protected override void OnFocusLost(FocusLostEvent e) - { - finalise(); - base.OnFocusLost(e); - } - - /// - /// Updates the bind target to the currently hovered key button or the first if clicked anywhere else. - /// - private void updateBindTarget() - { - if (bindTarget != null) bindTarget.IsBinding = false; - bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); - if (bindTarget != null) bindTarget.IsBinding = true; - } - - private class CancelButton : TriangleButton - { - public CancelButton() - { - Text = "Cancel"; - Size = new Vector2(80, 20); - } - } - - public class ClearButton : DangerousTriangleButton - { - public ClearButton() - { - Text = "Clear"; - Size = new Vector2(80, 20); - } - } - - public class KeyButton : Container - { - public readonly Framework.Input.Bindings.KeyBinding KeyBinding; - - private readonly Box box; - public readonly OsuSpriteText Text; - - private Color4 hoverColour; - - private bool isBinding; - - public bool IsBinding - { - get => isBinding; - set - { - if (value == isBinding) return; - - isBinding = value; - - updateHoverState(); - } - } - - public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) - { - KeyBinding = keyBinding; - - Margin = new MarginPadding(padding); - - // todo: use this in a meaningful way - // var isDefault = keyBinding.Action is Enum; - - Masking = true; - CornerRadius = padding; - - Height = height; - AutoSizeAxes = Axes.X; - - Children = new Drawable[] - { - new Container - { - AlwaysPresent = true, - Width = 80, - Height = height, - }, - box = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - Text = new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 10), - Margin = new MarginPadding(5), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = keyBinding.KeyCombination.ReadableString(), - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.YellowDark; - } - - protected override bool OnHover(HoverEvent e) - { - updateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateHoverState(); - base.OnHoverLost(e); - } - - private void updateHoverState() - { - if (isBinding) - { - box.FadeColour(Color4.White, transition_time, Easing.OutQuint); - Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint); - } - else - { - box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint); - Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint); - } - } - - public void UpdateKeyCombination(KeyCombination newCombination) - { - KeyBinding.KeyCombination = newCombination; - Text.Text = KeyBinding.KeyCombination.ReadableString(); - } - } - } -} diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index f799b4810f..216eabcf67 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -1,64 +1,460 @@ -// 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 System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.KeyBinding { - public class KeyBindingRow : Container, IFilterable + public class KeyBindingRow : Container { - private readonly object key; - private readonly ICollection bindings; - public readonly BasicKeyBindingRow BasicKeyBindingRow; + private readonly object action; + private readonly IEnumerable bindings; - private bool matchingFilter; + private const float transition_time = 150; - public bool MatchingFilter + private const float height = 20; + + private const float padding = 5; + + private FillFlowContainer cancelAndClearButtons; + private FillFlowContainer buttons; + + public Bindable IsDefault { get; } = new BindableBool(true); + + public KeyBindingRow(object action, IEnumerable bindings) { - get => matchingFilter; - set + this.action = action; + this.bindings = bindings; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = padding; + } + + [Resolved] + private KeyBindingStore store { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + updateIsDefaultValue(); + + EdgeEffect = new EdgeEffectParameters { - matchingFilter = value; - this.FadeTo(!matchingFilter ? 0 : 1); + Radius = 2, + Colour = colours.YellowDark.Opacity(0), + Type = EdgeEffectType.Shadow, + Hollow = true, + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + new OsuSpriteText + { + Text = action.GetDescription(), + Margin = new MarginPadding(padding), + }, + buttons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }, + cancelAndClearButtons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(padding) { Top = height + padding * 2 }, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Alpha = 0, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new CancelButton { Action = finalise }, + new ClearButton { Action = clear }, + }, + } + }; + foreach (var b in bindings) + buttons.Add(new KeyButton(b)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsDefault.BindValueChanged(isDefault => + { + if (isDefault.NewValue) + { + finalise(); + } + }); + } + + public void RestoreDefaults() + { + int i = 0; + + foreach (var d in Defaults) + { + var button = buttons[i++]; + button.UpdateKeyCombination(d); + store.Update(button.KeyBinding); + } + + updateIsDefaultValue(); + } + + protected override bool OnHover(HoverEvent e) + { + FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); + + base.OnHoverLost(e); + } + + public override bool AcceptsFocus => bindTarget == null; + + private KeyButton bindTarget; + + public bool AllowMainMouseButtons; + + public IEnumerable Defaults; + + private bool isModifier(Key k) => k < Key.F1; + + protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (!HasFocus || !bindTarget.IsHovered) + return base.OnMouseDown(e); + + if (!AllowMainMouseButtons) + { + switch (e.Button) + { + case MouseButton.Left: + case MouseButton.Right: + return true; + } + } + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + // don't do anything until the last button is released. + if (!HasFocus || e.HasAnyButtonPressed) + { + base.OnMouseUp(e); + return; + } + + if (bindTarget.IsHovered) + finalise(); + // prevent updating bind target before clear button's action + else if (!cancelAndClearButtons.Any(b => b.IsHovered)) + updateBindTarget(); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (HasFocus) + { + if (bindTarget.IsHovered) + { + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta)); + finalise(); + return true; + } + } + + return base.OnScroll(e); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + if (!isModifier(e.Key)) finalise(); + + return true; + } + + protected override void OnKeyUp(KeyUpEvent e) + { + if (!HasFocus) + { + base.OnKeyUp(e); + return; + } + + finalise(); + } + + protected override bool OnJoystickPress(JoystickPressEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + finalise(); + + return true; + } + + protected override void OnJoystickRelease(JoystickReleaseEvent e) + { + if (!HasFocus) + { + base.OnJoystickRelease(e); + return; + } + + finalise(); + } + + protected override bool OnMidiDown(MidiDownEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + finalise(); + + return true; + } + + protected override void OnMidiUp(MidiUpEvent e) + { + if (!HasFocus) + { + base.OnMidiUp(e); + return; + } + + finalise(); + } + + private void clear() + { + if (bindTarget == null) + return; + + bindTarget.UpdateKeyCombination(InputKey.None); + finalise(); + } + + private void finalise() + { + if (bindTarget != null) + { + store.Update(bindTarget.KeyBinding); + + updateIsDefaultValue(); + + bindTarget.IsBinding = false; + Schedule(() => + { + // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) + bindTarget = null; + }); + } + + if (HasFocus) + GetContainingInputManager().ChangeFocus(null); + + cancelAndClearButtons.FadeOut(300, Easing.OutQuint); + cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; + } + + private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); + + private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); + + protected override void OnFocus(FocusEvent e) + { + AutoSizeDuration = 500; + AutoSizeEasing = Easing.OutQuint; + + cancelAndClearButtons.FadeIn(300, Easing.OutQuint); + cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y; + + updateBindTarget(); + base.OnFocus(e); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + finalise(); + base.OnFocusLost(e); + } + + /// + /// Updates the bind target to the currently hovered key button or the first if clicked anywhere else. + /// + private void updateBindTarget() + { + if (bindTarget != null) bindTarget.IsBinding = false; + bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); + if (bindTarget != null) bindTarget.IsBinding = true; + } + + private class CancelButton : TriangleButton + { + public CancelButton() + { + Text = "Cancel"; + Size = new Vector2(80, 20); } } - public bool FilteringActive { get; set; } - - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - - public KeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) { - this.key = key; - this.bindings = bindings; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; - - InternalChildren = new Drawable[] + public class ClearButton : DangerousTriangleButton + { + public ClearButton() { - new RestoreDefaultValueButton() + Text = "Clear"; + Size = new Vector2(80, 20); + } + } + + public class KeyButton : Container + { + public readonly Framework.Input.Bindings.KeyBinding KeyBinding; + + private readonly Box box; + public readonly OsuSpriteText Text; + + private Color4 hoverColour; + + private bool isBinding; + + public bool IsBinding + { + get => isBinding; + set { - Current = BasicKeyBindingRow.IsDefault, - Action = () => { BasicKeyBindingRow.RestoreDefaults(); } - }, - new FillFlowContainer + if (value == isBinding) return; + + isBinding = value; + + updateHoverState(); + } + } + + public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) + { + KeyBinding = keyBinding; + + Margin = new MarginPadding(padding); + + // todo: use this in a meaningful way + // var isDefault = keyBinding.Action is Enum; + + Masking = true; + CornerRadius = padding; + + Height = height; + AutoSizeAxes = Axes.X; + + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = BasicKeyBindingRow = new BasicKeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) + new Container { - AllowMainMouseButtons = ruleset != null, - Defaults = defaults - } - }, - }; + AlwaysPresent = true, + Width = 80, + Height = height, + }, + box = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + Text = new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 10), + Margin = new MarginPadding(5), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = keyBinding.KeyCombination.ReadableString(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.YellowDark; + } + + protected override bool OnHover(HoverEvent e) + { + updateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateHoverState(); + base.OnHoverLost(e); + } + + private void updateHoverState() + { + if (isBinding) + { + box.FadeColour(Color4.White, transition_time, Easing.OutQuint); + Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint); + } + else + { + box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint); + Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint); + } + } + + public void UpdateKeyCombination(KeyCombination newCombination) + { + KeyBinding.KeyCombination = newCombination; + Text.Text = KeyBinding.KeyCombination.ReadableString(); + } } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index b1a5895449..fc370bd87e 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -38,12 +38,12 @@ namespace osu.Game.Overlays.KeyBinding foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings, Ruleset, defaultGroup.Select(d => d.KeyCombination))); + Add(new RestorableKeyBindingRow(defaultGroup.Key, bindings, Ruleset, defaultGroup.Select(d => d.KeyCombination))); } Add(new ResetButton { - Action = () => Children.OfType().ForEach(k => k.BasicKeyBindingRow.RestoreDefaults()) + Action = () => Children.OfType().ForEach(k => k.BasicKeyBindingRow.RestoreDefaults()) }); } } diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs new file mode 100644 index 0000000000..9bfdda727c --- /dev/null +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -0,0 +1,64 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.KeyBinding +{ + public class RestorableKeyBindingRow : Container, IFilterable + { + private readonly object key; + private readonly ICollection bindings; + public readonly KeyBindingRow BasicKeyBindingRow; + + private bool matchingFilter; + + public bool MatchingFilter + { + get => matchingFilter; + set + { + matchingFilter = value; + this.FadeTo(!matchingFilter ? 0 : 1); + } + } + + public bool FilteringActive { get; set; } + + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); + + public RestorableKeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) { + this.key = key; + this.bindings = bindings; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; + + InternalChildren = new Drawable[] + { + new RestoreDefaultValueButton() + { + Current = BasicKeyBindingRow.IsDefault, + Action = () => { BasicKeyBindingRow.RestoreDefaults(); } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Child = BasicKeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) + { + AllowMainMouseButtons = ruleset != null, + Defaults = defaults + } + }, + }; + } + } +} From d5feb8353d874c00f73b2d79623e47e3457871a4 Mon Sep 17 00:00:00 2001 From: Swords Date: Tue, 25 May 2021 21:37:08 +1000 Subject: [PATCH 342/429] Formatting, renaming --- .../Visual/Settings/TestSceneKeyBindingPanel.cs | 4 ++-- osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs | 2 +- .../Overlays/KeyBinding/RestorableKeyBindingRow.cs | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index bd3e94dffa..3edba2ddd7 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); } [Test] @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.BasicKeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); } [Test] diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index fc370bd87e..737c640b5a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays.KeyBinding Add(new ResetButton { - Action = () => Children.OfType().ForEach(k => k.BasicKeyBindingRow.RestoreDefaults()) + Action = () => Children.OfType().ForEach(k => k.KeyBindingRow.RestoreDefaults()) }); } } diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index 9bfdda727c..2981c77e15 100644 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.KeyBinding { private readonly object key; private readonly ICollection bindings; - public readonly KeyBindingRow BasicKeyBindingRow; + public readonly KeyBindingRow KeyBindingRow; private bool matchingFilter; @@ -32,7 +32,8 @@ namespace osu.Game.Overlays.KeyBinding public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - public RestorableKeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) { + public RestorableKeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) + { this.key = key; this.bindings = bindings; @@ -44,15 +45,15 @@ namespace osu.Game.Overlays.KeyBinding { new RestoreDefaultValueButton() { - Current = BasicKeyBindingRow.IsDefault, - Action = () => { BasicKeyBindingRow.RestoreDefaults(); } + Current = KeyBindingRow.IsDefault, + Action = () => { KeyBindingRow.RestoreDefaults(); } }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = BasicKeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) + Child = KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) { AllowMainMouseButtons = ruleset != null, Defaults = defaults From 9c2dca8229b5b956f1715bc67acc5cf42b7460c5 Mon Sep 17 00:00:00 2001 From: Swords Date: Tue, 25 May 2021 21:53:00 +1000 Subject: [PATCH 343/429] Removing redundant argument list --- osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index 2981c77e15..62a56ab055 100644 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays.KeyBinding InternalChildren = new Drawable[] { - new RestoreDefaultValueButton() + new RestoreDefaultValueButton { Current = KeyBindingRow.IsDefault, Action = () => { KeyBindingRow.RestoreDefaults(); } From 07a24d2747acc4e38d5a88ca67b290617b76de19 Mon Sep 17 00:00:00 2001 From: Swords Date: Tue, 25 May 2021 23:54:13 +1000 Subject: [PATCH 344/429] Fixing errors --- .../Overlays/KeyBinding/RestorableKeyBindingRow.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index 62a56ab055..09b2efd7fa 100644 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -41,6 +41,12 @@ namespace osu.Game.Overlays.KeyBinding AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; + KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) + { + AllowMainMouseButtons = ruleset != null, + Defaults = defaults + }; + InternalChildren = new Drawable[] { new RestoreDefaultValueButton @@ -53,11 +59,7 @@ namespace osu.Game.Overlays.KeyBinding RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) - { - AllowMainMouseButtons = ruleset != null, - Defaults = defaults - } + Child = KeyBindingRow }, }; } From 9223d85f37d33481fd0704349adaf19880728102 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 13:22:58 +0900 Subject: [PATCH 345/429] Remove all local type update logic from `TaikoBeatmapConverter` I believe the original goal was to keep this in the converter with the idea that samples may not always be hard coupled to the strong/rim states. But for now I think we can assume this coupling is going to continue into the near future, so let's keep all the logic in `TaikoHitObject`. --- .../Beatmaps/TaikoBeatmapConverter.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index b51f096d7d..90c99316b1 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // Old osu! used hit sounding to determine various hit type information IList samples = obj.Samples; - bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); - switch (obj) { case IHasDistance distanceData: @@ -94,15 +92,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { IList currentSamples = allSamples[i]; - bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); - strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); yield return new Hit { StartTime = j, - Type = isRim ? HitType.Rim : HitType.Centre, Samples = currentSamples, - IsStrong = strong }; i = (i + 1) % allSamples.Count; @@ -117,7 +111,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { StartTime = obj.StartTime, Samples = obj.Samples, - IsStrong = strong, Duration = taikoDuration, TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4 }; @@ -143,16 +136,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps default: { - bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE; - - bool isRim = samples.Any(isRimDefinition); - yield return new Hit { StartTime = obj.StartTime, - Type = isRim ? HitType.Rim : HitType.Centre, Samples = samples, - IsStrong = strong }; break; From 912748b4280b152f3dda8bb9d49b074d5f6e80e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 13:24:22 +0900 Subject: [PATCH 346/429] Avoid bindable feedback causing overwrites --- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 35 ------------------- .../Objects/TaikoHitObject.cs | 26 ++++++++++++-- .../Objects/TaikoStrongableHitObject.cs | 6 ++-- 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 8ede21fdad..6b6c04e92e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,45 +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 osu.Game.Audio; - namespace osu.Game.Rulesets.Taiko.Objects { public class Hit : TaikoStrongableHitObject { - protected override void UpdateTypeFromSamples() - { - base.UpdateTypeFromSamples(); - - Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre; - } - - protected override void UpdateSamplesFromType() - { - base.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 diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 46b864e7de..71214a4017 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.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; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -35,15 +37,35 @@ namespace osu.Game.Rulesets.Taiko.Objects protected TaikoHitObject() { SamplesBindable.BindCollectionChanged((_, __) => UpdateTypeFromSamples()); - TypeBindable.BindValueChanged(_ => UpdateSamplesFromType()); + TypeBindable.BindValueChanged(_ => updateSamplesFromType()); } - protected virtual void 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); + } + } } protected virtual void UpdateTypeFromSamples() { + Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre; } + + /// + /// 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(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index 237000474d..5cddc00a1e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects protected TaikoStrongableHitObject() { - IsStrongBindable.BindValueChanged(_ => UpdateSamplesFromType()); + IsStrongBindable.BindValueChanged(_ => updateSamplesFromType()); } protected override void UpdateTypeFromSamples() @@ -48,10 +48,8 @@ namespace osu.Game.Rulesets.Taiko.Objects IsStrong = getStrongSamples().Any(); } - protected override void UpdateSamplesFromType() + private void updateSamplesFromType() { - base.UpdateSamplesFromType(); - var strongSamples = getStrongSamples(); if (IsStrongBindable.Value != strongSamples.Any()) From cbad7bb7f0d4f58707c737809e9384936a8e476c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 13:40:36 +0900 Subject: [PATCH 347/429] Move taiko `Type` to `Hit` and localise all bind handling --- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 49 +++++++++++++++++++ .../Objects/TaikoHitObject.cs | 48 ------------------ .../Objects/TaikoStrongableHitObject.cs | 5 +- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 6b6c04e92e..b4ed242893 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,10 +1,59 @@ // 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 { public class Hit : TaikoStrongableHitObject { + public readonly Bindable TypeBindable = new Bindable(); + + /// + /// The that actuates this . + /// + public HitType Type + { + get => TypeBindable.Value; + set => TypeBindable.Value = value; + } + + public Hit() + { + TypeBindable.BindValueChanged(_ => updateSamplesFromType()); + SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); + } + + private void updateTypeFromSamples() + { + Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre; + } + + /// + /// 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(); + + 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); + } + } + } + protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime }; public class StrongNestedHit : StrongNestedHitObject diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 71214a4017..f047c03f4b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -1,9 +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.Bindables; -using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -14,17 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Objects { public abstract class TaikoHitObject : HitObject { - public readonly Bindable TypeBindable = new Bindable(); - - /// - /// The that actuates this . - /// - public HitType Type - { - get => TypeBindable.Value; - set => TypeBindable.Value = value; - } - /// /// Default size of a drawable taiko hit object. /// @@ -33,39 +19,5 @@ namespace osu.Game.Rulesets.Taiko.Objects public override Judgement CreateJudgement() => new TaikoJudgement(); protected override HitWindows CreateHitWindows() => new TaikoHitWindows(); - - protected TaikoHitObject() - { - SamplesBindable.BindCollectionChanged((_, __) => UpdateTypeFromSamples()); - TypeBindable.BindValueChanged(_ => 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); - } - } - } - - protected virtual void UpdateTypeFromSamples() - { - Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre; - } - - /// - /// 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(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index 5cddc00a1e..6c17573b50 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -39,12 +39,11 @@ namespace osu.Game.Rulesets.Taiko.Objects protected TaikoStrongableHitObject() { IsStrongBindable.BindValueChanged(_ => updateSamplesFromType()); + SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); } - protected override void UpdateTypeFromSamples() + private void updateTypeFromSamples() { - base.UpdateTypeFromSamples(); - IsStrong = getStrongSamples().Any(); } From 200592114f9e20d0f2ede64a70135f50f0319e00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 14:13:00 +0900 Subject: [PATCH 348/429] 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 7b09955d59c7538b8ff6d526c71193e9943ead34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 14:17:40 +0900 Subject: [PATCH 349/429] Remove redundant default bindable value --- 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 751ac1d10a..d4ccf4970b 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays { public class NewsOverlay : OnlineOverlay { - private readonly Bindable article = new Bindable(null); + private readonly Bindable article = new Bindable(); private readonly Container sidebarContainer; private readonly NewsSidebar sidebar; From 8ffa7f4a5ae5087e7c6f3e3e3f44f012fa95e1e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 14:28:20 +0900 Subject: [PATCH 350/429] Tidy up code --- osu.Game/Overlays/NewsOverlay.cs | 43 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index d4ccf4970b..400505ba52 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -6,7 +6,6 @@ using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.News; using osu.Game.Overlays.News.Displays; @@ -22,9 +21,14 @@ namespace osu.Game.Overlays private readonly NewsSidebar sidebar; private readonly Container content; - private APIRequest lastRequest; + private GetNewsRequest lastRequest; + private Cursor lastCursor; - private int? year; + + /// + /// The year currently being displayed. If null, the main listing is being displayed. + /// + private int? displayedYear; private CancellationTokenSource cancellationToken; @@ -100,7 +104,7 @@ namespace osu.Game.Overlays public void ShowYear(int year) { - loadFrontPage(year); + loadListing(year); Show(); } @@ -130,18 +134,18 @@ namespace osu.Game.Overlays private void onArticleChanged(ValueChangedEvent article) { if (article.NewValue == null) - loadFrontPage(); + loadListing(); else loadArticle(article.NewValue); } - private void loadFrontPage(int? year = null) + private void loadListing(int? year = null) { beginLoading(); Header.SetFrontPage(); - this.year = year; + displayedYear = year; lastCursor = null; performListingRequest(response => @@ -165,19 +169,6 @@ namespace osu.Game.Overlays }); } - private void performListingRequest(Action onSuccess) - { - lastRequest = new GetNewsRequest(year, lastCursor); - - ((GetNewsRequest)lastRequest).Success += response => Schedule(() => - { - lastCursor = response.Cursor; - onSuccess?.Invoke(response); - }); - - API.PerformAsync(lastRequest); - } - private void loadArticle(string article) { beginLoading(); @@ -188,6 +179,18 @@ namespace osu.Game.Overlays LoadDisplay(Empty()); } + private void performListingRequest(Action onSuccess) + { + lastRequest = new GetNewsRequest(displayedYear, lastCursor); + lastRequest.Success += response => Schedule(() => + { + lastCursor = response.Cursor; + onSuccess?.Invoke(response); + }); + + API.PerformAsync(lastRequest); + } + private void beginLoading() { lastRequest?.Cancel(); From 0f21510b8bc3a57347027c19ab9ecca306e32637 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 14:29:46 +0900 Subject: [PATCH 351/429] Move code around --- osu.Game/Overlays/NewsOverlay.cs | 36 +++++++++++++++----------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 400505ba52..ede9432a17 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -74,7 +74,13 @@ namespace osu.Game.Overlays base.LoadComplete(); // should not be run until first pop-in to avoid requesting data before user views. - article.BindValueChanged(onArticleChanged); + article.BindValueChanged(a => + { + if (a.NewValue == null) + loadListing(); + else + loadArticle(a.NewValue); + }); } protected override NewsHeader CreateHeader() => new NewsHeader { ShowFrontPage = ShowFrontPage }; @@ -131,14 +137,6 @@ 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 article) - { - if (article.NewValue == null) - loadListing(); - else - loadArticle(article.NewValue); - } - private void loadListing(int? year = null) { beginLoading(); @@ -159,16 +157,6 @@ namespace osu.Game.Overlays }); } - private void getMorePosts() - { - lastRequest?.Cancel(); - performListingRequest(response => - { - if (content.Child is ArticleListing listing) - listing.AddPosts(response); - }); - } - private void loadArticle(string article) { beginLoading(); @@ -179,6 +167,16 @@ namespace osu.Game.Overlays LoadDisplay(Empty()); } + private void getMorePosts() + { + lastRequest?.Cancel(); + performListingRequest(response => + { + if (content.Child is ArticleListing listing) + listing.AddPosts(response); + }); + } + private void performListingRequest(Action onSuccess) { lastRequest = new GetNewsRequest(displayedYear, lastCursor); From d165a758233d355f9fc9e70ad94740da2d955ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 14:37:52 +0900 Subject: [PATCH 352/429] Inline request flow to make it easier to understand --- osu.Game/Overlays/NewsOverlay.cs | 60 +++++++++++++++----------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index ede9432a17..8d0d242e39 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays private readonly NewsSidebar sidebar; private readonly Container content; - private GetNewsRequest lastRequest; + private GetNewsRequest request; private Cursor lastCursor; @@ -139,66 +139,64 @@ namespace osu.Game.Overlays private void loadListing(int? year = null) { - beginLoading(); - Header.SetFrontPage(); displayedYear = year; lastCursor = null; - performListingRequest(response => + beginLoading(true); + + request = new GetNewsRequest(displayedYear); + request.Success += response => Schedule(() => { + lastCursor = response.Cursor; sidebar.Metadata.Value = response.SidebarMetadata; - var listing = new ArticleListing(response); - listing.RequestMorePosts += getMorePosts; - - LoadDisplay(listing); + LoadDisplay(new ArticleListing(response) + { + RequestMorePosts = getMorePosts + }); }); - } - private void loadArticle(string article) - { - beginLoading(); - - Header.SetArticle(article); - - // Temporary, should be handled by ArticleDisplay later - LoadDisplay(Empty()); + API.PerformAsync(request); } private void getMorePosts() { - lastRequest?.Cancel(); - performListingRequest(response => + beginLoading(false); + + request = new GetNewsRequest(displayedYear, lastCursor); + request.Success += response => Schedule(() => { + lastCursor = response.Cursor; if (content.Child is ArticleListing listing) listing.AddPosts(response); }); + + API.PerformAsync(request); } - private void performListingRequest(Action onSuccess) + private void loadArticle(string article) { - lastRequest = new GetNewsRequest(displayedYear, lastCursor); - lastRequest.Success += response => Schedule(() => - { - lastCursor = response.Cursor; - onSuccess?.Invoke(response); - }); + // This is not yet implemented nor called from anywhere. + beginLoading(true); - API.PerformAsync(lastRequest); + Header.SetArticle(article); + LoadDisplay(Empty()); } - private void beginLoading() + private void beginLoading(bool showLoadingOverlay) { - lastRequest?.Cancel(); + request?.Cancel(); cancellationToken?.Cancel(); - Loading.Show(); + + if (showLoadingOverlay) + Loading.Show(); } protected override void Dispose(bool isDisposing) { - lastRequest?.Cancel(); + request?.Cancel(); cancellationToken?.Cancel(); base.Dispose(isDisposing); } From e4780abdfddf1642ff454e2fcad4e882adbe58e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 14:43:59 +0900 Subject: [PATCH 353/429] Split out `base` call from `switch` statement --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index d4ad0bee4d..b9037a5c77 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -23,12 +23,10 @@ namespace osu.Game.Overlays.Wiki.Markdown { case YamlFrontMatterBlock yamlFrontMatterBlock: container.Add(CreateNotice(yamlFrontMatterBlock)); - break; - - default: - base.AddMarkdownComponent(markdownObject, container, level); - break; + return; } + + base.AddMarkdownComponent(markdownObject, container, level); } public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer(); From b36b40cb3430e6bd511390f82c39ba23195dc8cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 15:20:47 +0900 Subject: [PATCH 354/429] 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 b3b39c4c137d0068c188fb3c8b52d0e8739930b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 15:42:26 +0900 Subject: [PATCH 355/429] Fix `BeatmapCarousel` accessing `ScreenSpaceDrawQuad` of non-loaded children Fixes failure seen at https://ci.appveyor.com/project/peppy/osu/builds/39302762/tests. --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index a3fca3d4e1..5875685965 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty(); + public IEnumerable DrawableBeatmaps => beatmapContainer?.AliveChildren ?? Enumerable.Empty(); [CanBeNull] private Container beatmapContainer; From 04f16c07836e57ea8ae8ef571ef2769c86fba054 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 13:55:16 +0700 Subject: [PATCH 356/429] 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 357/429] 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); From eeb6647bc50986ff123e483888df59f1af4b721b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 14:59:36 +0700 Subject: [PATCH 358/429] remove schedule in set current path --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index b9037a5c77..dfec437fe4 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Wiki.Markdown { public string CurrentPath { - set => Schedule(() => DocumentUrl += $"wiki/{value}"); + set => DocumentUrl = $"{DocumentUrl}wiki/{value}"; } protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) From 47cbbee4d12760107bfe8c1294c7035224759b01 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 15:01:16 +0700 Subject: [PATCH 359/429] remove CreateNotice method and move implementation to local --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index dfec437fe4..fbfdc5feaf 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Wiki.Markdown switch (markdownObject) { case YamlFrontMatterBlock yamlFrontMatterBlock: - container.Add(CreateNotice(yamlFrontMatterBlock)); + container.Add(new WikiNoticeContainer(yamlFrontMatterBlock)); return; } @@ -33,8 +33,6 @@ namespace osu.Game.Overlays.Wiki.Markdown protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) => new WikiMarkdownParagraph(paragraphBlock); - protected virtual FillFlowContainer CreateNotice(YamlFrontMatterBlock yamlFrontMatterBlock) => new WikiNoticeContainer(yamlFrontMatterBlock); - private class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer { protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline)); From f8a3a3779721ec862d6e920aa5ede5f57ff90f11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 17:34:17 +0900 Subject: [PATCH 360/429] Remove outdated comment --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 546049ea9b..5a2a9baf44 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -386,9 +386,6 @@ namespace osu.Game.Overlays.KeyBinding Margin = new MarginPadding(padding); - // todo: use this in a meaningful way - // var isDefault = keyBinding.Action is Enum; - Masking = true; CornerRadius = padding; From 71f77eb902c8e680b31f1ae39e2d68e28de9f3b4 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 15:04:04 +0700 Subject: [PATCH 361/429] fix image test --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 67030631b0..ebabfc9479 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -106,6 +106,7 @@ needs_cleanup: true { AddStep("Add absolute image", () => { + markdownContainer.DocumentUrl = "https://osu.ppy.sh"; markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)"; }); } @@ -115,6 +116,7 @@ needs_cleanup: true { AddStep("Add relative image", () => { + markdownContainer.DocumentUrl = "https://osu.ppy.sh"; markdownContainer.CurrentPath = "Interface/"; markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); @@ -125,6 +127,7 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { + markdownContainer.DocumentUrl = "https://osu.ppy.sh"; markdownContainer.CurrentPath = "Interface/"; markdownContainer.Text = @"Line before image @@ -139,6 +142,7 @@ Line after image"; { AddStep("Add inline image", () => { + markdownContainer.DocumentUrl = "https://osu.ppy.sh"; markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; }); } @@ -147,6 +151,11 @@ Line after image"; { public LinkInline Link; + public new string DocumentUrl + { + set => base.DocumentUrl = value; + } + public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer { UrlAdded = link => Link = link, @@ -162,6 +171,8 @@ Line after image"; UrlAdded?.Invoke(linkInline); } + + protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline)); } } } From 49b4a6ea67babd011e9f815b04d2ac60884c5c3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 17:07:24 +0900 Subject: [PATCH 362/429] Replace local namespace qualifiers with `using` --- osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index 09b2efd7fa..70b4fabd6d 100644 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; using osu.Game.Rulesets; namespace osu.Game.Overlays.KeyBinding @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.KeyBinding public class RestorableKeyBindingRow : Container, IFilterable { private readonly object key; - private readonly ICollection bindings; + private readonly ICollection bindings; public readonly KeyBindingRow KeyBindingRow; private bool matchingFilter; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.KeyBinding public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - public RestorableKeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) + public RestorableKeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) { this.key = key; this.bindings = bindings; From 9c31b8856d8d4dc2d91a344249f02590c7809e61 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 15:10:09 +0700 Subject: [PATCH 363/429] change image url replace implementation --- .../Overlays/Wiki/Markdown/WikiMarkdownImage.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs index 361aa2e95f..c2115efeb5 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs @@ -2,37 +2,28 @@ // See the LICENCE file in the repository root for full licence text. using Markdig.Syntax.Inlines; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Cursor; -using osu.Game.Online.API; namespace osu.Game.Overlays.Wiki.Markdown { public class WikiMarkdownImage : MarkdownImage, IHasTooltip { - private readonly string url; - public string TooltipText { get; } public WikiMarkdownImage(LinkInline linkInline) : base(linkInline.Url) { - url = linkInline.Url; TooltipText = linkInline.Title; } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + protected override ImageContainer CreateImageContainer(string url) { - // The idea is replace "{api.WebsiteRootUrl}/wiki/{path-to-image}" to "{api.WebsiteRootUrl}/wiki/images/{path-to-image}" + // The idea is replace "https://website.url/wiki/{path-to-image}" to "https://website.url/wiki/images/{path-to-image}" // "/wiki/images/*" is route to fetch wiki image from osu!web server (see: https://github.com/ppy/osu-web/blob/4205eb66a4da86bdee7835045e4bf28c35456e04/routes/web.php#L289) - // Currently all image in dev server (https://dev.ppy.sh/wiki/image/*) is 404 - // So for now just replace "{api.WebsiteRootUrl}/wiki/*" to "https://osu.ppy.sh/wiki/images/*" for simplicity - var imageUrl = url.Replace($"{api.WebsiteRootUrl}/wiki", "https://osu.ppy.sh/wiki/images"); + url = url.Replace("/wiki/", "/wiki/images/"); - InternalChild = new DelayedLoadWrapper(CreateImageContainer(imageUrl)); + return base.CreateImageContainer(url); } } } From 17334fd2e6b3ee4280ca5ebcbe0b70a7bb04fd22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 17:12:12 +0900 Subject: [PATCH 364/429] Inline `KeyBindingRow` construction --- .../Overlays/KeyBinding/RestorableKeyBindingRow.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs index 70b4fabd6d..5d1dc6a4d1 100644 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs @@ -42,12 +42,6 @@ namespace osu.Game.Overlays.KeyBinding AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; - KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) - { - AllowMainMouseButtons = ruleset != null, - Defaults = defaults - }; - InternalChildren = new Drawable[] { new RestoreDefaultValueButton @@ -60,7 +54,11 @@ namespace osu.Game.Overlays.KeyBinding RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = KeyBindingRow + Child = KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) + { + AllowMainMouseButtons = ruleset != null, + Defaults = defaults + } }, }; } From 02806fedb06b7c90ac6e32217fe7d41e783ad59f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 17:17:02 +0900 Subject: [PATCH 365/429] Add missing newline --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 5a2a9baf44..e8f6a4d065 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -101,6 +101,7 @@ namespace osu.Game.Overlays.KeyBinding }, } }; + foreach (var b in bindings) buttons.Add(new KeyButton(b)); } From 7c9383b586ad2ab0cf73e22c3b3505957f26f0cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 18:03:15 +0900 Subject: [PATCH 366/429] Combine `RestorableKeyBindingRow` back into `KeyBindingRow` --- .../Settings/TestSceneKeyBindingPanel.cs | 14 +- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 132 ++++++++++++------ .../KeyBinding/KeyBindingsSubsection.cs | 10 +- .../KeyBinding/RestorableKeyBindingRow.cs | 66 --------- .../Overlays/RestoreDefaultValueButton.cs | 3 + 5 files changed, 104 insertions(+), 121 deletions(-) delete mode 100644 osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 4d0321b29d..acf9deb3cb 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -142,11 +142,11 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestSingleBindingResetButton() { - RestorableKeyBindingRow settingsKeyBindingRow = null; + KeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); @@ -165,17 +165,17 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); } [Test] public void TestResetAllBindingsButton() { - RestorableKeyBindingRow settingsKeyBindingRow = null; + KeyBindingRow settingsKeyBindingRow = null; AddStep("click first row", () => { - settingsKeyBindingRow = panel.ChildrenOfType().First(); + settingsKeyBindingRow = panel.ChildrenOfType().First(); InputManager.MoveMouseTo(settingsKeyBindingRow); InputManager.Click(MouseButton.Left); @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0); - AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.KeyBindingRow.Defaults.ElementAt(0))); + AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); } [Test] @@ -261,4 +261,4 @@ namespace osu.Game.Tests.Visual.Settings }); } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index e8f6a4d065..c9ed64cc3f 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Overlays.KeyBinding { - public class KeyBindingRow : Container + public class KeyBindingRow : Container, IFilterable { private readonly object action; private readonly IEnumerable bindings; @@ -35,20 +35,40 @@ namespace osu.Game.Overlays.KeyBinding private const float padding = 5; + private bool matchingFilter; + + public bool MatchingFilter + { + get => matchingFilter; + set + { + matchingFilter = value; + this.FadeTo(!matchingFilter ? 0 : 1); + } + } + + private Container content; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + content.ReceivePositionalInputAt(screenSpacePos); + + public bool FilteringActive { get; set; } + + private OsuSpriteText text; private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; public Bindable IsDefault { get; } = new BindableBool(true); + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString()); + public KeyBindingRow(object action, IEnumerable bindings) { this.action = action; this.bindings = bindings; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - - Masking = true; - CornerRadius = padding; } [Resolved] @@ -59,46 +79,65 @@ namespace osu.Game.Overlays.KeyBinding { updateIsDefaultValue(); - EdgeEffect = new EdgeEffectParameters - { - Radius = 2, - Colour = colours.YellowDark.Opacity(0), - Type = EdgeEffectType.Shadow, - Hollow = true, - }; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }; - Children = new Drawable[] + InternalChildren = new Drawable[] { - new Box + new RestoreDefaultValueButton { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - new OsuSpriteText - { - Text = action.GetDescription(), - Margin = new MarginPadding(padding), - }, - buttons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }, - cancelAndClearButtons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding(padding) { Top = height + padding * 2 }, - Anchor = Anchor.TopRight, + Current = IsDefault, + Action = RestoreDefaults, Origin = Anchor.TopRight, - Alpha = 0, - Spacing = new Vector2(5), + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + CornerRadius = padding, + EdgeEffect = new EdgeEffectParameters + { + Radius = 2, + Colour = colours.YellowDark.Opacity(0), + Type = EdgeEffectType.Shadow, + Hollow = true, + }, Children = new Drawable[] { - new CancelButton { Action = finalise }, - new ClearButton { Action = clear }, - }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + text = new OsuSpriteText + { + Text = action.GetDescription(), + Margin = new MarginPadding(padding), + }, + buttons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }, + cancelAndClearButtons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(padding) { Top = height + padding * 2 }, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Alpha = 0, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new CancelButton { Action = finalise }, + new ClearButton { Action = clear }, + }, + } + } } }; @@ -135,14 +174,14 @@ namespace osu.Game.Overlays.KeyBinding protected override bool OnHover(HoverEvent e) { - FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); + content.FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); + content.FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); base.OnHoverLost(e); } @@ -307,14 +346,10 @@ namespace osu.Game.Overlays.KeyBinding cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; } - private void updateIsDefaultValue() => IsDefault.Value = computeIsDefaultValue(); - - private bool computeIsDefaultValue() => bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); - protected override void OnFocus(FocusEvent e) { - AutoSizeDuration = 500; - AutoSizeEasing = Easing.OutQuint; + content.AutoSizeDuration = 500; + content.AutoSizeEasing = Easing.OutQuint; cancelAndClearButtons.FadeIn(300, Easing.OutQuint); cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y; @@ -339,6 +374,11 @@ namespace osu.Game.Overlays.KeyBinding if (bindTarget != null) bindTarget.IsBinding = true; } + private void updateIsDefaultValue() + { + IsDefault.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); + } + private class CancelButton : TriangleButton { public CancelButton() diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 737c640b5a..5e1f9d8f75 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -37,13 +37,19 @@ namespace osu.Game.Overlays.KeyBinding foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { + int intKey = (int)defaultGroup.Key; + // one row per valid action. - Add(new RestorableKeyBindingRow(defaultGroup.Key, bindings, Ruleset, defaultGroup.Select(d => d.KeyCombination))); + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals(intKey))) + { + AllowMainMouseButtons = Ruleset != null, + Defaults = defaultGroup.Select(d => d.KeyCombination) + }); } Add(new ResetButton { - Action = () => Children.OfType().ForEach(k => k.KeyBindingRow.RestoreDefaults()) + Action = () => Children.OfType().ForEach(k => k.RestoreDefaults()) }); } } diff --git a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs deleted file mode 100644 index 5d1dc6a4d1..0000000000 --- a/osu.Game/Overlays/KeyBinding/RestorableKeyBindingRow.cs +++ /dev/null @@ -1,66 +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.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets; - -namespace osu.Game.Overlays.KeyBinding -{ - public class RestorableKeyBindingRow : Container, IFilterable - { - private readonly object key; - private readonly ICollection bindings; - public readonly KeyBindingRow KeyBindingRow; - - private bool matchingFilter; - - public bool MatchingFilter - { - get => matchingFilter; - set - { - matchingFilter = value; - this.FadeTo(!matchingFilter ? 0 : 1); - } - } - - public bool FilteringActive { get; set; } - - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(key.ToString()); - - public RestorableKeyBindingRow(object key, ICollection bindings, RulesetInfo ruleset, IEnumerable defaults) - { - this.key = key; - this.bindings = bindings; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; - - InternalChildren = new Drawable[] - { - new RestoreDefaultValueButton - { - Current = KeyBindingRow.IsDefault, - Action = () => { KeyBindingRow.RestoreDefaults(); } - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = KeyBindingRow = new KeyBindingRow(key, bindings.Where(b => ((int)b.Action).Equals((int)key))) - { - AllowMainMouseButtons = ruleset != null, - Defaults = defaults - } - }, - }; - } - } -} diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 0fe7b7322f..213ad2ba68 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -21,6 +21,9 @@ namespace osu.Game.Overlays private readonly BindableWithCurrent current = new BindableWithCurrent(); + // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. + public override bool AcceptsFocus => true; + public Bindable Current { get => current.Current; From c05dfee22042f0ccdf305e9474b7b3d0b18de9ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 18:28:00 +0900 Subject: [PATCH 367/429] Simplify default handling flow --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index c9ed64cc3f..0df3359c28 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - public Bindable IsDefault { get; } = new BindableBool(true); + private Bindable isDefault { get; } = new BindableBool(true); public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString()); @@ -77,8 +77,6 @@ namespace osu.Game.Overlays.KeyBinding [BackgroundDependencyLoader] private void load(OsuColour colours) { - updateIsDefaultValue(); - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }; @@ -87,7 +85,7 @@ namespace osu.Game.Overlays.KeyBinding { new RestoreDefaultValueButton { - Current = IsDefault, + Current = isDefault, Action = RestoreDefaults, Origin = Anchor.TopRight, }, @@ -143,19 +141,8 @@ namespace osu.Game.Overlays.KeyBinding foreach (var b in bindings) buttons.Add(new KeyButton(b)); - } - protected override void LoadComplete() - { - base.LoadComplete(); - - IsDefault.BindValueChanged(isDefault => - { - if (isDefault.NewValue) - { - finalise(); - } - }); + updateIsDefaultValue(); } public void RestoreDefaults() @@ -169,7 +156,7 @@ namespace osu.Game.Overlays.KeyBinding store.Update(button.KeyBinding); } - updateIsDefaultValue(); + isDefault.Value = true; } protected override bool OnHover(HoverEvent e) @@ -376,7 +363,7 @@ namespace osu.Game.Overlays.KeyBinding private void updateIsDefaultValue() { - IsDefault.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); + isDefault.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); } private class CancelButton : TriangleButton From a77de24746a1aa085e7b707f811c53e3a6b41e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 18:58:18 +0900 Subject: [PATCH 368/429] Fix `SlowLoadPlayer` potentially not being instantiated in time for test --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index cfdea31a75..1e0aee2149 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -176,11 +176,9 @@ namespace osu.Game.Tests.Visual.Gameplay { SlowLoadPlayer slowPlayer = null; - AddStep("load slow dummy beatmap", () => - { - LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); - Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); - }); + AddStep("load slow dummy beatmap", () => LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)))); + AddUntilStep("wait for slow player to be instantiated", () => slowPlayer != null); + AddStep("schedule slow load", () => Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000)); AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen()); } From 878079d3d7d32e04bcac1b05b9fc26f91bae9586 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 19:08:00 +0900 Subject: [PATCH 369/429] Fix correct beatmap not being set if running test alone --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1e0aee2149..8a7e4da693 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -88,13 +88,18 @@ namespace osu.Game.Tests.Visual.Gameplay { beforeLoadAction?.Invoke(); + prepareBeatmap(); + + LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); + } + + private void prepareBeatmap() + { Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning; foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); - - LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); } [Test] @@ -176,7 +181,12 @@ namespace osu.Game.Tests.Visual.Gameplay { SlowLoadPlayer slowPlayer = null; - AddStep("load slow dummy beatmap", () => LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)))); + AddStep("load slow dummy beatmap", () => + { + prepareBeatmap(); + LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + }); + AddUntilStep("wait for slow player to be instantiated", () => slowPlayer != null); AddStep("schedule slow load", () => Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000)); From 7ed4cbf7bff7a6800ae7c12d92dddbb2ddf9e1d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 19:25:05 +0900 Subject: [PATCH 370/429] Fix settings panel hide animation looking wrong when a sub-panel is visible when hidden --- osu.Game/Overlays/SettingsPanel.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index f0a11d67b7..eae828c142 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -49,8 +50,6 @@ namespace osu.Game.Overlays private readonly bool showSidebar; - protected Box Background; - protected SettingsPanel(bool showSidebar) { this.showSidebar = showSidebar; @@ -63,13 +62,13 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - InternalChild = ContentContainer = new Container + InternalChild = ContentContainer = new NonMaskedContent { Width = WIDTH, RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - Background = new Box + new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -165,7 +164,7 @@ namespace osu.Game.Overlays { base.PopOut(); - ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + ContentContainer.MoveToX(-WIDTH + ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); @@ -191,6 +190,12 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } + private class NonMaskedContent : Container + { + // masking breaks the pan-out transform with nested sub-settings panels. + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + } + public class SettingsSectionsContainer : SectionsContainer { public SearchContainer SearchContainer; From fbfbd992235b2a4ac2b7467521ae715f61795f4c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 19:20:39 +0700 Subject: [PATCH 371/429] change document url test to dev server --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index ebabfc9479..57bd8cd077 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -106,7 +106,7 @@ needs_cleanup: true { AddStep("Add absolute image", () => { - markdownContainer.DocumentUrl = "https://osu.ppy.sh"; + markdownContainer.DocumentUrl = "https://dev.ppy.sh"; markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)"; }); } @@ -116,7 +116,7 @@ needs_cleanup: true { AddStep("Add relative image", () => { - markdownContainer.DocumentUrl = "https://osu.ppy.sh"; + markdownContainer.DocumentUrl = "https://dev.ppy.sh"; markdownContainer.CurrentPath = "Interface/"; markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); @@ -127,7 +127,7 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { - markdownContainer.DocumentUrl = "https://osu.ppy.sh"; + markdownContainer.DocumentUrl = "https://dev.ppy.sh"; markdownContainer.CurrentPath = "Interface/"; markdownContainer.Text = @"Line before image @@ -142,7 +142,7 @@ Line after image"; { AddStep("Add inline image", () => { - markdownContainer.DocumentUrl = "https://osu.ppy.sh"; + markdownContainer.DocumentUrl = "https://dev.ppy.sh"; markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; }); } From 62fb09774a7588e9bb9babd29ded257c0325901d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 19:22:21 +0700 Subject: [PATCH 372/429] create WikiMarkdownImageBlock --- .../Wiki/Markdown/WikiMarkdownImageBlock.cs | 49 +++++++++++++++++++ .../Wiki/Markdown/WikiMarkdownParagraph.cs | 40 --------------- 2 files changed, 49 insertions(+), 40 deletions(-) create mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs delete mode 100644 osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs new file mode 100644 index 0000000000..179762103a --- /dev/null +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Syntax.Inlines; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; +using osuTK; + +namespace osu.Game.Overlays.Wiki.Markdown +{ + public class WikiMarkdownImageBlock : FillFlowContainer + { + [Resolved] + private IMarkdownTextComponent parentTextComponent { get; set; } + + private readonly LinkInline linkInline; + + public WikiMarkdownImageBlock(LinkInline linkInline) + { + this.linkInline = linkInline; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(0, 3); + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new WikiMarkdownImage(linkInline) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + parentTextComponent.CreateSpriteText().With(t => + { + t.Text = linkInline.Title; + t.Anchor = Anchor.TopCentre; + t.Origin = Anchor.TopCentre; + }), + }; + } + } +} diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs deleted file mode 100644 index 4a7ce24aba..0000000000 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs +++ /dev/null @@ -1,40 +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.Linq; -using Markdig.Syntax; -using Markdig.Syntax.Inlines; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers.Markdown; -using osuTK; - -namespace osu.Game.Overlays.Wiki.Markdown -{ - public class WikiMarkdownParagraph : MarkdownParagraph - { - private readonly ParagraphBlock paragraphBlock; - - public WikiMarkdownParagraph(ParagraphBlock paragraphBlock) - : base(paragraphBlock) - { - this.paragraphBlock = paragraphBlock; - } - - [BackgroundDependencyLoader] - private void load() - { - MarkdownTextFlowContainer textFlow; - InternalChild = textFlow = CreateTextFlow(); - textFlow.AddInlineText(paragraphBlock.Inline); - - // Check if paragraph only contains an image. - if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline) - { - textFlow.TextAnchor = Anchor.TopCentre; - textFlow.Spacing = new Vector2(0, 5); - textFlow.AddText($"\n{linkInline.Title}"); - } - } - } -} From 2344a1a411cf1778ffd21635e2167f13e0039f89 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 26 May 2021 19:22:33 +0700 Subject: [PATCH 373/429] use image block in markdown container --- .../Wiki/Markdown/WikiMarkdownContainer.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index fbfdc5feaf..4e671cca6d 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.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.Linq; using Markdig.Extensions.Yaml; using Markdig.Syntax; using Markdig.Syntax.Inlines; @@ -23,7 +24,17 @@ namespace osu.Game.Overlays.Wiki.Markdown { case YamlFrontMatterBlock yamlFrontMatterBlock: container.Add(new WikiNoticeContainer(yamlFrontMatterBlock)); - return; + break; + + case ParagraphBlock paragraphBlock: + // Check if paragraph only contains an image + if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline) + { + container.Add(new WikiMarkdownImageBlock(linkInline)); + return; + } + + break; } base.AddMarkdownComponent(markdownObject, container, level); @@ -31,8 +42,6 @@ namespace osu.Game.Overlays.Wiki.Markdown public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer(); - protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) => new WikiMarkdownParagraph(paragraphBlock); - private class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer { protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline)); From 1bde11a07e202e9c643d3602d66cd2948b83b807 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 26 May 2021 15:35:38 +0300 Subject: [PATCH 374/429] Refactor ArticleListing --- .../Overlays/News/Displays/ArticleListing.cs | 58 ++++++++++++------- osu.Game/Overlays/NewsOverlay.cs | 9 ++- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index b554b462a9..4bbc80c5f3 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -2,13 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.News.Displays @@ -20,20 +23,12 @@ namespace osu.Game.Overlays.News.Displays { public Action RequestMorePosts; + private readonly BindableList posts = new BindableList(); + private bool showMoreButtonIsVisible; + private FillFlowContainer content; private ShowMoreButton showMore; - private readonly GetNewsResponse initialResponse; - - /// - /// Instantiate a listing for the specified year. - /// - /// Initial response to create articles from. - public ArticleListing(GetNewsResponse initialResponse) - { - this.initialResponse = initialResponse; - } - [BackgroundDependencyLoader] private void load() { @@ -45,7 +40,6 @@ namespace osu.Game.Overlays.News.Displays Left = 30, Right = 50 }; - InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -61,8 +55,7 @@ namespace osu.Game.Overlays.News.Displays RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = initialResponse.NewsPosts.Select(p => new NewsCard(p)).ToList() + Spacing = new Vector2(0, 10) }, showMore = new ShowMoreButton { @@ -73,24 +66,45 @@ namespace osu.Game.Overlays.News.Displays Top = 15 }, Action = RequestMorePosts, - Alpha = initialResponse.Cursor != null ? 1 : 0 + Alpha = 0 } } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + posts.BindCollectionChanged((sender, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + addPosts(args.NewItems.Cast()); + break; + + default: + throw new NotSupportedException(@"You can only add items to this list. Other actions are not supported."); + } + }, true); + } + + public void AddPosts(IEnumerable posts, bool showMoreButtonIsVisible) + { + this.showMoreButtonIsVisible = showMoreButtonIsVisible; + this.posts.AddRange(posts); + } + private CancellationTokenSource cancellationToken; - public void AddPosts(GetNewsResponse response) + private void addPosts(IEnumerable posts) { - cancellationToken?.Cancel(); - - LoadComponentsAsync(response.NewsPosts.Select(p => new NewsCard(p)).ToList(), loaded => + LoadComponentsAsync(posts.Select(p => new NewsCard(p)).ToList(), loaded => { content.AddRange(loaded); - showMore.IsLoading = false; - showMore.Alpha = response.Cursor != null ? 1 : 0; + showMore.Alpha = showMoreButtonIsVisible ? 1 : 0; }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 8d0d242e39..34bacd5540 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -152,10 +152,9 @@ namespace osu.Game.Overlays lastCursor = response.Cursor; sidebar.Metadata.Value = response.SidebarMetadata; - LoadDisplay(new ArticleListing(response) - { - RequestMorePosts = getMorePosts - }); + var listing = new ArticleListing { RequestMorePosts = getMorePosts }; + listing.AddPosts(response.NewsPosts, response.Cursor != null); + LoadDisplay(listing); }); API.PerformAsync(request); @@ -170,7 +169,7 @@ namespace osu.Game.Overlays { lastCursor = response.Cursor; if (content.Child is ArticleListing listing) - listing.AddPosts(response); + listing.AddPosts(response.NewsPosts, response.Cursor != null); }); API.PerformAsync(request); From 8e923a5d8ff233db20652edbc33d7d217830580a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 22:24:51 +0900 Subject: [PATCH 375/429] Instantiate immediately, rather than waiting for instantiation --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 8a7e4da693..8160a62991 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -184,10 +184,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load slow dummy beatmap", () => { prepareBeatmap(); - LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + slowPlayer = new SlowLoadPlayer(false, false); + LoadScreen(loader = new TestPlayerLoader(() => slowPlayer)); }); - AddUntilStep("wait for slow player to be instantiated", () => slowPlayer != null); AddStep("schedule slow load", () => Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000)); AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen()); From 71de541245060ea4b48d38820c233e30d5ee29bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 22:35:11 +0900 Subject: [PATCH 376/429] Minor spacing / reformatting --- osu.Game/Overlays/News/Displays/ArticleListing.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index 4bbc80c5f3..3524b8652c 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -34,12 +34,14 @@ namespace osu.Game.Overlays.News.Displays { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Vertical = 20, Left = 30, Right = 50 }; + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -61,10 +63,7 @@ namespace osu.Game.Overlays.News.Displays { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Margin = new MarginPadding - { - Top = 15 - }, + Margin = new MarginPadding { Top = 15 }, Action = RequestMorePosts, Alpha = 0 } From 9947867e8408e8f2d0d4296061c9f67ea3bf1ffe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 22:46:43 +0900 Subject: [PATCH 377/429] Remove unnecessary bindable flow --- .../Overlays/News/Displays/ArticleListing.cs | 42 +++---------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index 3524b8652c..48a3f5498a 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using System.Threading; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -23,9 +21,6 @@ namespace osu.Game.Overlays.News.Displays { public Action RequestMorePosts; - private readonly BindableList posts = new BindableList(); - private bool showMoreButtonIsVisible; - private FillFlowContainer content; private ShowMoreButton showMore; @@ -71,41 +66,16 @@ namespace osu.Game.Overlays.News.Displays }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - posts.BindCollectionChanged((sender, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - addPosts(args.NewItems.Cast()); - break; - - default: - throw new NotSupportedException(@"You can only add items to this list. Other actions are not supported."); - } - }, true); - } - - public void AddPosts(IEnumerable posts, bool showMoreButtonIsVisible) - { - this.showMoreButtonIsVisible = showMoreButtonIsVisible; - this.posts.AddRange(posts); - } - - private CancellationTokenSource cancellationToken; - - private void addPosts(IEnumerable posts) - { + public void AddPosts(IEnumerable posts, bool morePostsAvailable) => Schedule(() => LoadComponentsAsync(posts.Select(p => new NewsCard(p)).ToList(), loaded => { content.AddRange(loaded); showMore.IsLoading = false; - showMore.Alpha = showMoreButtonIsVisible ? 1 : 0; - }, (cancellationToken = new CancellationTokenSource()).Token); - } + showMore.Alpha = morePostsAvailable ? 1 : 0; + }, (cancellationToken = new CancellationTokenSource()).Token) + ); + + private CancellationTokenSource cancellationToken; protected override void Dispose(bool isDisposing) { From 735e7b9c741e2bc0d4d29df6e880daf5ff44a665 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 22:49:39 +0900 Subject: [PATCH 378/429] Pass fetch more action in via ctor to avoid potential nullref --- osu.Game/Overlays/News/Displays/ArticleListing.cs | 13 +++++++++---- osu.Game/Overlays/NewsOverlay.cs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index 48a3f5498a..dc3b17b323 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -19,11 +19,18 @@ namespace osu.Game.Overlays.News.Displays ///
public class ArticleListing : CompositeDrawable { - public Action RequestMorePosts; + private readonly Action fetchMorePosts; private FillFlowContainer content; private ShowMoreButton showMore; + private CancellationTokenSource cancellationToken; + + public ArticleListing(Action fetchMorePosts) + { + this.fetchMorePosts = fetchMorePosts; + } + [BackgroundDependencyLoader] private void load() { @@ -59,7 +66,7 @@ namespace osu.Game.Overlays.News.Displays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Margin = new MarginPadding { Top = 15 }, - Action = RequestMorePosts, + Action = fetchMorePosts, Alpha = 0 } } @@ -75,8 +82,6 @@ namespace osu.Game.Overlays.News.Displays }, (cancellationToken = new CancellationTokenSource()).Token) ); - private CancellationTokenSource cancellationToken; - protected override void Dispose(bool isDisposing) { cancellationToken?.Cancel(); diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 34bacd5540..12e3f81ca1 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Overlays lastCursor = response.Cursor; sidebar.Metadata.Value = response.SidebarMetadata; - var listing = new ArticleListing { RequestMorePosts = getMorePosts }; + var listing = new ArticleListing(getMorePosts); listing.AddPosts(response.NewsPosts, response.Cursor != null); LoadDisplay(listing); }); From c0a8382175a55e0323ab11ed456f3584489ea356 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 00:12:22 +0900 Subject: [PATCH 379/429] Remove local API construction --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 57bd8cd077..c423d46aa3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -23,9 +23,6 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); - [Cached] - private readonly IAPIProvider api = new DummyAPIAccess(); - [SetUp] public void Setup() => Schedule(() => { @@ -55,16 +52,16 @@ namespace osu.Game.Tests.Visual.Online AddStep("set current path", () => markdownContainer.CurrentPath = "Article_styling_criteria/"); AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)"); - AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/Main_Page"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page"); AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)"); - AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/FAQ"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ"); AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)"); - AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing"); AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)"); - AddAssert("check url", () => markdownContainer.Link.Url == $"{api.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting"); } [Test] From 74fc0a17d5f668c3e178c428c2e6abb30e6871db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 00:55:05 +0900 Subject: [PATCH 380/429] Remove unused using statement --- osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index c423d46aa3..1e19af933a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers.Markdown; -using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; From 9ac4ef273e72435d21f09e70c7297b53bcb32049 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 26 May 2021 23:21:05 +0300 Subject: [PATCH 381/429] Make DrawableSliderTail not require ITrackSnaking --- .../Objects/Drawables/DrawableSliderTail.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index d81af053d1..cd6bf1d8d2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -7,13 +7,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, IHasMainCirclePiece { public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject; @@ -111,7 +112,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } - public void UpdateSnakingPosition(Vector2 start, Vector2 end) => - Position = HitObject.RepeatIndex % 2 == 0 ? end : start; + protected override void OnApply() + { + base.OnApply(); + + if (Slider != null) + Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0); + } } } From a7865d3f22013bf85fb46ff1433ab3976d2c090b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 27 May 2021 08:46:24 +0700 Subject: [PATCH 382/429] move colour provider to BDL --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index 71b492b375..ad30306475 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -21,9 +21,6 @@ namespace osu.Game.Overlays.Wiki { public class WikiPanelContainer : Container { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - private WikiPanelMarkdownContainer panelContainer; public string Text; @@ -37,7 +34,7 @@ namespace osu.Game.Overlays.Wiki } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Children = new Drawable[] { From 0c2d3ae0e757b486b4e28180780387a8addcdf7e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 27 May 2021 05:08:49 +0300 Subject: [PATCH 383/429] Revert "Move beatmap skin info creation to static method at `IBeatmapSkin`" This reverts commit 9806d94b745348e7c44af36cc93b76401234d225. --- osu.Game/Skinning/BeatmapSkinExtensions.cs | 16 ---------------- osu.Game/Skinning/LegacyBeatmapSkin.cs | 5 ++++- 2 files changed, 4 insertions(+), 17 deletions(-) delete mode 100644 osu.Game/Skinning/BeatmapSkinExtensions.cs diff --git a/osu.Game/Skinning/BeatmapSkinExtensions.cs b/osu.Game/Skinning/BeatmapSkinExtensions.cs deleted file mode 100644 index 18ef09c392..0000000000 --- a/osu.Game/Skinning/BeatmapSkinExtensions.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.Game.Beatmaps; - -namespace osu.Game.Skinning -{ - public static class BeatmapSkinExtensions - { - public static SkinInfo CreateSkinInfo(BeatmapInfo beatmap) => new SkinInfo - { - Name = beatmap.ToString(), - Creator = beatmap.Metadata?.AuthorString, - }; - } -} diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 5ee436e8bb..3ec205e897 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning protected override bool UseCustomSampleBanks => true; public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, IStorageResourceProvider resources) - : base(BeatmapSkinExtensions.CreateSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path) + : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path) { // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer) Configuration.AllowDefaultComboColoursFallback = false; @@ -49,5 +49,8 @@ namespace osu.Game.Skinning return base.GetSample(sampleInfo); } + + private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => + new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; } } From d66f07fccb10cae2aaf76020bab1c03313a11b56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 14:04:50 +0900 Subject: [PATCH 384/429] Move text and `isFullWidth` parameters to constructor --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 14 +++---------- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 22 ++++++++++++-------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 8ff85c1404..bf4a76c3d8 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -77,10 +77,8 @@ namespace osu.Game.Overlays.Wiki { yield return new Drawable[] { - new WikiPanelContainer + new WikiPanelContainer(panelsNode[i].InnerText, true) { - Text = panelsNode[i].InnerText, - IsFullWidth = true, Width = 2, }, null, @@ -91,14 +89,8 @@ namespace osu.Game.Overlays.Wiki { yield return new Drawable[] { - new WikiPanelContainer - { - Text = panelsNode[i].InnerText, - }, - new WikiPanelContainer - { - Text = panelsNode[i + 1].InnerText, - }, + new WikiPanelContainer(panelsNode[i].InnerText), + new WikiPanelContainer(panelsNode[i + 1].InnerText), }; } } diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index ad30306475..db213e4951 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -23,12 +23,15 @@ namespace osu.Game.Overlays.Wiki { private WikiPanelMarkdownContainer panelContainer; - public string Text; + private readonly string text; - public bool IsFullWidth; + private readonly bool isFullWidth; - public WikiPanelContainer() + public WikiPanelContainer(string text, bool isFullWidth = false) { + this.text = text; + this.isFullWidth = isFullWidth; + RelativeSizeAxes = Axes.X; Padding = new MarginPadding(3); } @@ -56,12 +59,11 @@ namespace osu.Game.Overlays.Wiki RelativeSizeAxes = Axes.Both, }, }, - panelContainer = new WikiPanelMarkdownContainer + panelContainer = new WikiPanelMarkdownContainer(isFullWidth) { - Text = Text, + Text = text, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - IsFullWidth = IsFullWidth, } }; } @@ -74,10 +76,12 @@ namespace osu.Game.Overlays.Wiki private class WikiPanelMarkdownContainer : WikiMarkdownContainer { - public bool IsFullWidth; + private readonly bool isFullWidth; - public WikiPanelMarkdownContainer() + public WikiPanelMarkdownContainer(bool isFullWidth) { + this.isFullWidth = isFullWidth; + LineSpacing = 0; DocumentPadding = new MarginPadding(30); DocumentMargin = new MarginPadding(0); @@ -92,7 +96,7 @@ namespace osu.Game.Overlays.Wiki protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock) { - IsFullWidth = IsFullWidth, + IsFullWidth = isFullWidth, }; } From 122bb05aa81d78af1671cf8e00f21e06134a5798 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 15:20:35 +0900 Subject: [PATCH 385/429] Add a mention that `OnApply/OnFree` is performed after `ApplyDefaults` --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index cc663c37af..cca55819c5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -311,6 +311,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Invoked for this to take on any values from a newly-applied . + /// This is also fired after any changes which occurred via an call. /// protected virtual void OnApply() { @@ -318,6 +319,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Invoked for this to revert any values previously taken on from the currently-applied . + /// This is also fired after any changes which occurred via an call. /// protected virtual void OnFree() { From 4fbd43fcaec8cc6af378cd750bf3165a061ff35d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 27 May 2021 13:24:06 +0700 Subject: [PATCH 386/429] add inline comment for width 2 --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index bf4a76c3d8..2356ec03a6 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -79,6 +79,7 @@ namespace osu.Game.Overlays.Wiki { new WikiPanelContainer(panelsNode[i].InnerText, true) { + // This is required to fill up the space of "null" drawable below. Width = 2, }, null, From c72e258bfb857e229bc8c46faffc9d12ec2ae238 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 27 May 2021 13:34:55 +0700 Subject: [PATCH 387/429] change for to while in create panels --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 2356ec03a6..76e6b0261a 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -69,7 +69,9 @@ namespace osu.Game.Overlays.Wiki { var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]").ToArray(); - for (var i = 0; i < panelsNode.Length; i++) + var i = 0; + + while (i < panelsNode.Length) { var isFullWidth = panelsNode[i].HasClass("wiki-main-page-panel--full"); @@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Wiki { yield return new Drawable[] { - new WikiPanelContainer(panelsNode[i].InnerText, true) + new WikiPanelContainer(panelsNode[i++].InnerText, true) { // This is required to fill up the space of "null" drawable below. Width = 2, @@ -85,13 +87,12 @@ namespace osu.Game.Overlays.Wiki null, }; } - - if (i % 2 == 1) + else { yield return new Drawable[] { - new WikiPanelContainer(panelsNode[i].InnerText), - new WikiPanelContainer(panelsNode[i + 1].InnerText), + new WikiPanelContainer(panelsNode[i++].InnerText), + new WikiPanelContainer(panelsNode[i++].InnerText), }; } } From 5c44083856ca22055275fa4271933e2f024dbd3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 16:12:49 +0900 Subject: [PATCH 388/429] Fix test potentially not waiting for drawable beatmaps to be loaded --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 44c9361ff8..78ddfa9ed2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -786,9 +786,12 @@ namespace osu.Game.Tests.Visual.SongSelect } } - private void checkVisibleItemCount(bool diff, int count) => - AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () => + private void checkVisibleItemCount(bool diff, int count) + { + // until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet. + AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () => carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); + } private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); From b55ac413e7ca8953450319d3212ed9a1208d1ea2 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 27 May 2021 14:20:36 +0700 Subject: [PATCH 389/429] add many scenario of main page layout --- .../Visual/Online/TestSceneWikiMainPage.cs | 99 +++++++++++++++++-- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs index 3a2bafb128..7fa1a6e135 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,7 +16,10 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); - public TestSceneWikiMainPage() + private BasicScrollContainer container; + + [SetUp] + public void Setup() => Schedule(() => { Children = new Drawable[] { @@ -24,20 +28,103 @@ namespace osu.Game.Tests.Visual.Online Colour = overlayColour.Background5, RelativeSizeAxes = Axes.Both, }, - new BasicScrollContainer + container = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(20), - Child = new WikiMainPage - { - Markdown = main_page_markdown - } } }; + }); + + [Test] + public void TestWikiMainPage() + { + AddStep("Show main page", () => + { + container.Child = new WikiMainPage + { + Markdown = main_page_markdown, + }; + }); + } + + [Test] + public void TestNonFullWidthLayout() + { + AddStep("Show Layout", () => + { + container.Child = new WikiMainPage + { + Markdown = non_full_width, + }; + }); + } + + [Test] + public void TestFullWidthAtTheEnd() + { + AddStep("Show layout", () => + { + container.Child = new WikiMainPage + { + Markdown = full_width_end, + }; + }); + } + + [Test] + public void TestFullWidthAtTheStartAndEnd() + { + AddStep("Show layout", () => + { + container.Child = new WikiMainPage + { + Markdown = full_width_start_end, + }; + }); + } + + [Test] + public void TestFullWidthInTheMiddle() + { + AddStep("Show layout", () => + { + container.Child = new WikiMainPage + { + Markdown = full_width_in_the_middle, + }; + }); + } + + [Test] + public void TestFullWidthStacking() + { + AddStep("Show layout", () => + { + container.Child = new WikiMainPage + { + Markdown = full_width_stack, + }; + }); } // From https://osu.ppy.sh/api/v2/wiki/en/Main_Page private const string main_page_markdown = "---\nlayout: main_page\n---\n\n\n\n
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; + + private const string non_full_width = + "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; + + private const string full_width_end = + "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n"; + + private const string full_width_start_end = + "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n"; + + private const string full_width_in_the_middle = + "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; + + private const string full_width_stack= + "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; } } From 820310543d56b23ed5fc99591da3f4e994a37ad2 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 27 May 2021 14:53:22 +0700 Subject: [PATCH 390/429] fix spacing style --- osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs index 7fa1a6e135..9fb3c685b7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Online private const string full_width_in_the_middle = "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; - private const string full_width_stack= + private const string full_width_stack = "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; } } From 046087a367b6a608620b05f014a985e60232fdea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 16:58:01 +0900 Subject: [PATCH 391/429] Fix access to `AliveChildren` before `IsLoaded` --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 5875685965..9773bd5ce9 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - public IEnumerable DrawableBeatmaps => beatmapContainer?.AliveChildren ?? Enumerable.Empty(); + public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren; [CanBeNull] private Container beatmapContainer; From 0b600db81487a766e9d91687c7a57e6ae8517cfb Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 27 May 2021 16:24:05 +0700 Subject: [PATCH 392/429] revert back main page test --- .../Visual/Online/TestSceneWikiMainPage.cs | 99 ++----------------- 1 file changed, 6 insertions(+), 93 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs index 9fb3c685b7..3a2bafb128 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,10 +15,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); - private BasicScrollContainer container; - - [SetUp] - public void Setup() => Schedule(() => + public TestSceneWikiMainPage() { Children = new Drawable[] { @@ -28,103 +24,20 @@ namespace osu.Game.Tests.Visual.Online Colour = overlayColour.Background5, RelativeSizeAxes = Axes.Both, }, - container = new BasicScrollContainer + new BasicScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(20), + Child = new WikiMainPage + { + Markdown = main_page_markdown + } } }; - }); - - [Test] - public void TestWikiMainPage() - { - AddStep("Show main page", () => - { - container.Child = new WikiMainPage - { - Markdown = main_page_markdown, - }; - }); - } - - [Test] - public void TestNonFullWidthLayout() - { - AddStep("Show Layout", () => - { - container.Child = new WikiMainPage - { - Markdown = non_full_width, - }; - }); - } - - [Test] - public void TestFullWidthAtTheEnd() - { - AddStep("Show layout", () => - { - container.Child = new WikiMainPage - { - Markdown = full_width_end, - }; - }); - } - - [Test] - public void TestFullWidthAtTheStartAndEnd() - { - AddStep("Show layout", () => - { - container.Child = new WikiMainPage - { - Markdown = full_width_start_end, - }; - }); - } - - [Test] - public void TestFullWidthInTheMiddle() - { - AddStep("Show layout", () => - { - container.Child = new WikiMainPage - { - Markdown = full_width_in_the_middle, - }; - }); - } - - [Test] - public void TestFullWidthStacking() - { - AddStep("Show layout", () => - { - container.Child = new WikiMainPage - { - Markdown = full_width_stack, - }; - }); } // From https://osu.ppy.sh/api/v2/wiki/en/Main_Page private const string main_page_markdown = "---\nlayout: main_page\n---\n\n\n\n
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; - - private const string non_full_width = - "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; - - private const string full_width_end = - "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n"; - - private const string full_width_start_end = - "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n"; - - private const string full_width_in_the_middle = - "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; - - private const string full_width_stack = - "
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; } } From 37ef368738139e0214dc046693d67e928c939813 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 19:03:59 +0900 Subject: [PATCH 393/429] Move async call out of `using` to better define the flow of data --- osu.Game/Collections/CollectionManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 3a63587b30..086cc573d5 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -58,8 +58,13 @@ namespace osu.Game.Collections if (storage.Exists(database_name)) { + List beatmapCollections; + using (var stream = storage.GetStream(database_name)) - importCollections(readCollections(stream)); + beatmapCollections = readCollections(stream); + + // intentionally fire-and-forget async. + importCollections(beatmapCollections); } } From e1836cd1b2535a2d0329e9d0a71979bd2796a414 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 27 May 2021 17:12:15 +0700 Subject: [PATCH 394/429] add debug assert --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index 76e6b0261a..bbbbc602bf 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -69,6 +70,8 @@ namespace osu.Game.Overlays.Wiki { var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]").ToArray(); + Debug.Assert(panelsNode.Length > 1); + var i = 0; while (i < panelsNode.Length) From 0c4d4ee0d2f4792964a5b2630ec90c780853d208 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 19:16:22 +0900 Subject: [PATCH 395/429] Fix collection import tests deadlocking due to `TaskCompletionSource` continuation triggering host disposal --- .../Collections/IO/ImportCollectionsTest.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index a8ee1bcc2e..040c0d7165 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = LoadOsuIntoHost(host); - await osu.CollectionManager.Import(new MemoryStream()); + await importCollectionsFromStream(osu, new MemoryStream()); Assert.That(osu.CollectionManager.Collections.Count, Is.Zero); } @@ -36,14 +36,14 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportWithNoBeatmaps() - { +{ using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { var osu = LoadOsuIntoHost(host); - await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = LoadOsuIntoHost(host, true); - await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Collections.IO ms.Seek(0, SeekOrigin.Begin); - await osu.CollectionManager.Import(ms); + await importCollectionsFromStream(osu, ms); } Assert.That(host.UpdateThread.Running, Is.True); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = LoadOsuIntoHost(host, true); - await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); // Move first beatmap from second collection into the first. osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]); @@ -169,5 +169,12 @@ namespace osu.Game.Tests.Collections.IO } } } + + private static async Task importCollectionsFromStream(TestOsuGameBase osu, Stream stream) + { + // intentionally spin this up on a separate task to avoid disposal deadlocks. + // see https://github.com/EventStore/EventStore/issues/1179 + await Task.Run(() => osu.CollectionManager.Import(stream).Wait()); + } } } From a2ed85bf460c128425a264a9409d6544b50cc4d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 19:34:39 +0900 Subject: [PATCH 396/429] Fix broken formatting --- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 040c0d7165..a47631a83b 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportWithNoBeatmaps() -{ + { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try From bcf1e3db1ed995edc4236f18f90e18150db5a4b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 19:45:53 +0900 Subject: [PATCH 397/429] Fix test failures in `TestSceneStoryboardWithOutro` Test was not accounting for the fact that the results may not have loaded in time. --- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 0ac8e01482..5ef3eff856 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); - AddAssert("score shown", () => Player.IsScoreShown); + AddUntilStep("wait for score shown", () => Player.IsScoreShown); + AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration); } [Test] From 121dd175e67be92c3796462b5fa6e7f04124c5d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 19:57:19 +0900 Subject: [PATCH 398/429] Fix test failure in `TestSceneMultiplayerGameplayLeaderboard` The transfer of users was not accounting for the fact that the `StartPlay` calls are now scheduled and not necessarily run in time. --- .../TestSceneMultiplayerGameplayLeaderboard.cs | 8 ++++++-- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 80b9aa8228..af2f6fa5fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -73,8 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer for (int i = 0; i < users; i++) spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers); + spectatorClient.Schedule(() => + { + Client.CurrentMatchPlayingUserIds.Clear(); + Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers); + }); Children = new Drawable[] { @@ -91,6 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); + AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0); } [Test] diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 3a5ffa8770..c7aa43b377 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -53,6 +54,8 @@ namespace osu.Game.Tests.Visual.Spectator }); } + public new void Schedule(Action action) => base.Schedule(action); + /// /// Sends frames for an arbitrary user. /// From ff1fa71e6f7b03eafd91739f20d0b7ded6cdbe60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 20:06:37 +0900 Subject: [PATCH 399/429] Remove feature request issue template and link to discussions --- .github/ISSUE_TEMPLATE/02-feature-request-issues.md | 7 ------- .github/ISSUE_TEMPLATE/config.yml | 9 ++++++++- 2 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/02-feature-request-issues.md diff --git a/.github/ISSUE_TEMPLATE/02-feature-request-issues.md b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md deleted file mode 100644 index c3357dd780..0000000000 --- a/.github/ISSUE_TEMPLATE/02-feature-request-issues.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: Feature Request -about: Propose a feature you would like to see in the game! ---- -**Describe the new feature:** - -**Proposal designs of the feature:** diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 69baeee60c..c62231e8e0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,12 @@ blank_issues_enabled: false contact_links: + - name: Suggestions or feature request + url: https://github.com/ppy/osu/discussions/categories/ideas + about: Got something you think should change or be added? Search for or start a new discussion! + - name: Help + url: https://github.com/ppy/osu/discussions/categories/q-a + about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section! - name: osu!stable issues url: https://github.com/ppy/osu-stable-issues - about: For issues regarding osu!stable (not osu!lazer), open them here. + about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports. + From d24a712dd4b8b7b2dcc3bf3203e63e56276ac0ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 02:27:06 +0900 Subject: [PATCH 400/429] Move protected properties to one location and mark setters `private` where feasible --- osu.Game/OsuGameBase.cs | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3c143c1db9..bbfaa62258 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -53,51 +53,53 @@ namespace osu.Game ///
public class OsuGameBase : Framework.Game, ICanAcceptFiles { - public const string CLIENT_STREAM_NAME = "lazer"; + public const string CLIENT_STREAM_NAME = @"lazer"; public const int SAMPLE_CONCURRENCY = 6; public bool UseDevelopmentServer { get; } - protected OsuConfigManager LocalConfig; + protected OsuConfigManager LocalConfig { get; private set; } protected SessionStatics SessionStatics { get; private set; } - protected BeatmapManager BeatmapManager; + protected BeatmapManager BeatmapManager { get; private set; } - protected ScoreManager ScoreManager; + protected ScoreManager ScoreManager { get; private set; } - protected BeatmapDifficultyCache DifficultyCache; + protected BeatmapDifficultyCache DifficultyCache { get; private set; } - protected UserLookupCache UserCache; + protected UserLookupCache UserCache { get; private set; } - protected SkinManager SkinManager; + protected SkinManager SkinManager { get; private set; } - protected RulesetStore RulesetStore; + protected RulesetStore RulesetStore { get; private set; } - protected FileStore FileStore; + protected FileStore FileStore { get; private set; } - protected KeyBindingStore KeyBindingStore; + protected KeyBindingStore KeyBindingStore { get; private set; } - protected SettingsStore SettingsStore; + protected SettingsStore SettingsStore { get; private set; } - protected RulesetConfigCache RulesetConfigCache; + protected RulesetConfigCache RulesetConfigCache { get; private set; } - protected IAPIProvider API; + protected MenuCursorContainer MenuCursorContainer { get; private set; } + + protected MusicController MusicController { get; private set; } + + protected IAPIProvider API { get; set; } + + protected Storage Storage { get; set; } + + protected Bindable Beatmap { get; private set; } // cached via load() method private SpectatorClient spectatorClient; private MultiplayerClient multiplayerClient; - protected MenuCursorContainer MenuCursorContainer; - - protected MusicController MusicController; - private Container content; protected override Container Content => content; - protected Storage Storage { get; set; } - [Cached] [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); @@ -119,8 +121,6 @@ namespace osu.Game ///
public readonly Bindable>> AvailableMods = new Bindable>>(); - protected Bindable Beatmap { get; private set; } // cached via load() method - private Bindable fpsDisplayVisible; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); From 4e49fbf7fbb0785019436e12e06117ce1f3f36a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 02:30:31 +0900 Subject: [PATCH 401/429] Switch protected properties to `private` where feasible --- osu.Game/OsuGameBase.cs | 55 +++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bbfaa62258..97f838032a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -67,22 +67,12 @@ namespace osu.Game protected ScoreManager ScoreManager { get; private set; } - protected BeatmapDifficultyCache DifficultyCache { get; private set; } - - protected UserLookupCache UserCache { get; private set; } - protected SkinManager SkinManager { get; private set; } protected RulesetStore RulesetStore { get; private set; } - protected FileStore FileStore { get; private set; } - protected KeyBindingStore KeyBindingStore { get; private set; } - protected SettingsStore SettingsStore { get; private set; } - - protected RulesetConfigCache RulesetConfigCache { get; private set; } - protected MenuCursorContainer MenuCursorContainer { get; private set; } protected MusicController MusicController { get; private set; } @@ -93,13 +83,6 @@ namespace osu.Game protected Bindable Beatmap { get; private set; } // cached via load() method - private SpectatorClient spectatorClient; - private MultiplayerClient multiplayerClient; - - private Container content; - - protected override Container Content => content; - [Cached] [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); @@ -121,6 +104,24 @@ namespace osu.Game /// public readonly Bindable>> AvailableMods = new Bindable>>(); + private BeatmapDifficultyCache difficultyCache; + + private UserLookupCache userCache; + + private FileStore fileStore; + + private SettingsStore settingsStore; + + private RulesetConfigCache rulesetConfigCache; + + private SpectatorClient spectatorClient; + + private MultiplayerClient multiplayerClient; + + protected override Container Content => content; + + private Container content; + private Bindable fpsDisplayVisible; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -246,10 +247,10 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage)); - dependencies.Cache(FileStore = new FileStore(contextFactory, Storage)); + dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap, true)); // this should likely be moved to ArchiveModelManager when another case appers where it is necessary @@ -273,19 +274,19 @@ namespace osu.Game ScoreManager.Undelete(getBeatmapScores(item), true); }); - dependencies.Cache(DifficultyCache = new BeatmapDifficultyCache()); - AddInternal(DifficultyCache); + dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); + AddInternal(difficultyCache); - dependencies.Cache(UserCache = new UserLookupCache()); - AddInternal(UserCache); + dependencies.Cache(userCache = new UserLookupCache()); + AddInternal(userCache); var scorePerformanceManager = new ScorePerformanceCache(); dependencies.Cache(scorePerformanceManager); AddInternal(scorePerformanceManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); - dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); - dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); + dependencies.Cache(settingsStore = new SettingsStore(contextFactory)); + dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore)); var powerStatus = CreateBatteryInfo(); if (powerStatus != null) @@ -308,7 +309,7 @@ namespace osu.Game dependencies.CacheAs>(Beatmap); dependencies.CacheAs(Beatmap); - FileStore.Cleanup(); + fileStore.Cleanup(); // add api components to hierarchy. if (API is APIAccess apiAccess) @@ -316,7 +317,7 @@ namespace osu.Game AddInternal(spectatorClient); AddInternal(multiplayerClient); - AddInternal(RulesetConfigCache); + AddInternal(rulesetConfigCache); GlobalActionContainer globalBindings; From b8edca59ebe0db43c26c8f21dc3519de90e659d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 02:37:14 +0900 Subject: [PATCH 402/429] General function reorganisation --- osu.Game/OsuGameBase.cs | 94 ++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 97f838032a..22502b661c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -57,8 +57,34 @@ namespace osu.Game public const int SAMPLE_CONCURRENCY = 6; + /// + /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. + /// + internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5; + public bool UseDevelopmentServer { get; } + public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); + + /// + /// MD5 representation of the game executable. + /// + public string VersionHash { get; private set; } + + public bool IsDeployedBuild => AssemblyVersion.Major > 0; + + public virtual string Version + { + get + { + if (!IsDeployedBuild) + return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release"); + + var version = AssemblyVersion; + return $@"{version.Major}.{version.Minor}.{version.Build}"; + } + } + protected OsuConfigManager LocalConfig { get; private set; } protected SessionStatics SessionStatics { get; private set; } @@ -118,32 +144,17 @@ namespace osu.Game private MultiplayerClient multiplayerClient; + private DatabaseContextFactory contextFactory; + protected override Container Content => content; private Container content; + private DependencyContainer dependencies; + private Bindable fpsDisplayVisible; - public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); - - /// - /// MD5 representation of the game executable. - /// - public string VersionHash { get; private set; } - - public bool IsDeployedBuild => AssemblyVersion.Major > 0; - - public virtual string Version - { - get - { - if (!IsDeployedBuild) - return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release"); - - var version = AssemblyVersion; - return $@"{version.Major}.{version.Minor}.{version.Build}"; - } - } + private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); public OsuGameBase() { @@ -151,23 +162,14 @@ namespace osu.Game Name = @"osu!lazer"; } - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - private DatabaseContextFactory contextFactory; - protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); protected virtual BatteryInfo CreateBatteryInfo() => null; - /// - /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. - /// - internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5; + protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer(); - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); [BackgroundDependencyLoader] private void load() @@ -345,6 +347,19 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // TODO: This is temporary until we reimplement the local FPS display. + // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. + fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); + fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; + fpsDisplayVisible.TriggerChange(); + + FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; + } + private void onRulesetChanged(ValueChangedEvent r) { var dict = new Dictionary>(); @@ -361,21 +376,6 @@ namespace osu.Game AvailableMods.Value = dict; } - protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - // TODO: This is temporary until we reimplement the local FPS display. - // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. - fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; - fpsDisplayVisible.TriggerChange(); - - FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; - } - private void runMigrations() { try From b4c13d837d54855a9b9b3310e5790b77f61efca9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 02:44:44 +0900 Subject: [PATCH 403/429] Move import logic out to partial class --- osu.Game/OsuGameBase.cs | 73 +------------------------------ osu.Game/OsuGameBase_Importing.cs | 57 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 72 deletions(-) create mode 100644 osu.Game/OsuGameBase_Importing.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 22502b661c..9f73236fb0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -41,7 +40,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Utils; -using osuTK.Input; using RuntimeInfo = osu.Framework.RuntimeInfo; namespace osu.Game @@ -51,7 +49,7 @@ namespace osu.Game /// Unlike , this class will not load any kind of UI, allowing it to be used /// for provide dependencies to test cases without interfering with them. /// - public class OsuGameBase : Framework.Game, ICanAcceptFiles + public partial class OsuGameBase : Framework.Game, ICanAcceptFiles { public const string CLIENT_STREAM_NAME = @"lazer"; @@ -399,75 +397,6 @@ namespace osu.Game } } - public override void SetHost(GameHost host) - { - base.SetHost(host); - - // may be non-null for certain tests - Storage ??= host.Storage; - - LocalConfig ??= UseDevelopmentServer - ? new DevelopmentOsuConfigManager(Storage) - : new OsuConfigManager(Storage); - } - - /// - /// Use to programatically exit the game as if the user was triggering via alt-f4. - /// Will keep persisting until an exit occurs (exit may be blocked multiple times). - /// - public void GracefullyExit() - { - if (!OnExiting()) - Exit(); - else - Scheduler.AddDelayed(GracefullyExit, 2000); - } - - protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); - - private readonly List fileImporters = new List(); - - /// - /// Register a global handler for file imports. Most recently registered will have precedence. - /// - /// The handler to register. - public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler); - - /// - /// Unregister a global handler for file imports. - /// - /// The previously registered handler. - public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler); - - public async Task Import(params string[] paths) - { - if (paths.Length == 0) - return; - - var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant()); - - foreach (var groups in filesPerExtension) - { - foreach (var importer in fileImporters) - { - if (importer.HandledExtensions.Contains(groups.Key)) - await importer.Import(groups.ToArray()).ConfigureAwait(false); - } - } - } - - public virtual async Task Import(params ImportTask[] tasks) - { - var tasksPerExtension = tasks.GroupBy(t => Path.GetExtension(t.Path).ToLowerInvariant()); - await Task.WhenAll(tasksPerExtension.Select(taskGroup => - { - var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key)); - return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask; - })).ConfigureAwait(false); - } - - public IEnumerable HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions); - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/OsuGameBase_Importing.cs b/osu.Game/OsuGameBase_Importing.cs new file mode 100644 index 0000000000..d712da3553 --- /dev/null +++ b/osu.Game/OsuGameBase_Importing.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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Game.Database; + +namespace osu.Game +{ + public partial class OsuGameBase + { + private readonly List fileImporters = new List(); + + /// + /// Register a global handler for file imports. Most recently registered will have precedence. + /// + /// The handler to register. + public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler); + + /// + /// Unregister a global handler for file imports. + /// + /// The previously registered handler. + public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler); + + public async Task Import(params string[] paths) + { + if (paths.Length == 0) + return; + + var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant()); + + foreach (var groups in filesPerExtension) + { + foreach (var importer in fileImporters) + { + if (importer.HandledExtensions.Contains(groups.Key)) + await importer.Import(groups.ToArray()).ConfigureAwait(false); + } + } + } + + public virtual async Task Import(params ImportTask[] tasks) + { + var tasksPerExtension = tasks.GroupBy(t => Path.GetExtension(t.Path).ToLowerInvariant()); + await Task.WhenAll(tasksPerExtension.Select(taskGroup => + { + var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key)); + return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask; + })).ConfigureAwait(false); + } + + public IEnumerable HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions); + } +} From fe6b1936cb3d69eec4646407cd2713b16112eba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 02:45:04 +0900 Subject: [PATCH 404/429] Move `OsuUserInputManager` out from nested class --- osu.Game/Input/OsuUserInputManager.cs | 38 +++++++++++++++++++++++++++ osu.Game/OsuGameBase.cs | 32 ---------------------- 2 files changed, 38 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Input/OsuUserInputManager.cs diff --git a/osu.Game/Input/OsuUserInputManager.cs b/osu.Game/Input/OsuUserInputManager.cs new file mode 100644 index 0000000000..621f1e1fc9 --- /dev/null +++ b/osu.Game/Input/OsuUserInputManager.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 osu.Framework.Input; +using osuTK.Input; + +namespace osu.Game.Input +{ + public class OsuUserInputManager : UserInputManager + { + internal OsuUserInputManager() + { + } + + protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button) + { + switch (button) + { + case MouseButton.Right: + return new RightMouseManager(button); + } + + return base.CreateButtonEventManagerFor(button); + } + + private class RightMouseManager : MouseButtonEventManager + { + public RightMouseManager(MouseButton button) + : base(button) + { + } + + public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers. + public override bool EnableClick => false; + public override bool ChangeFocusOnClick => false; + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9f73236fb0..659da1bc4e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -407,37 +407,5 @@ namespace osu.Game contextFactory.FlushConnections(); } - - private class OsuUserInputManager : UserInputManager - { - protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button) - { - switch (button) - { - case MouseButton.Right: - return new RightMouseManager(button); - } - - return base.CreateButtonEventManagerFor(button); - } - - private class RightMouseManager : MouseButtonEventManager - { - public RightMouseManager(MouseButton button) - : base(button) - { - } - - public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers. - public override bool EnableClick => false; - public override bool ChangeFocusOnClick => false; - } - } - - public void Migrate(string path) - { - contextFactory.FlushConnections(); - (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); - } } } From ce96c58230db88a1ea14211ea4f4d4d927240aa3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 02:46:26 +0900 Subject: [PATCH 405/429] Move public members up --- osu.Game/OsuGameBase.cs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 659da1bc4e..dfb9bd3a99 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -160,6 +160,36 @@ namespace osu.Game Name = @"osu!lazer"; } + public override void SetHost(GameHost host) + { + base.SetHost(host); + + // may be non-null for certain tests + Storage ??= host.Storage; + + LocalConfig ??= UseDevelopmentServer + ? new DevelopmentOsuConfigManager(Storage) + : new OsuConfigManager(Storage); + } + + /// + /// Use to programatically exit the game as if the user was triggering via alt-f4. + /// Will keep persisting until an exit occurs (exit may be blocked multiple times). + /// + public void GracefullyExit() + { + if (!OnExiting()) + Exit(); + else + Scheduler.AddDelayed(GracefullyExit, 2000); + } + + public void Migrate(string path) + { + contextFactory.FlushConnections(); + (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + } + protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); protected virtual BatteryInfo CreateBatteryInfo() => null; @@ -169,6 +199,8 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + [BackgroundDependencyLoader] private void load() { From ead0e92d7d523b3a29a2f44e82a16fbd815ec8ff Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 28 May 2021 00:50:59 +0700 Subject: [PATCH 406/429] use select single node for blurb --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index bbbbc602bf..b34b5d9acf 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Wiki private Container createBlurb(HtmlDocument html) { - var blurbNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page__blurb')]").First(); + var blurbNode = html.DocumentNode.SelectSingleNode("//div[contains(@class, 'wiki-main-page__blurb')]"); return new Container { From 854ef0abfdc67cf87c7e20552c6db320707eb8ba Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 28 May 2021 00:55:14 +0700 Subject: [PATCH 407/429] add simple bound check --- osu.Game/Overlays/Wiki/WikiMainPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index b34b5d9acf..c4c0b83ef4 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Wiki yield return new Drawable[] { new WikiPanelContainer(panelsNode[i++].InnerText), - new WikiPanelContainer(panelsNode[i++].InnerText), + i < panelsNode.Length ? new WikiPanelContainer(panelsNode[i++].InnerText) : null, }; } } From 5e8893b20336249b6139b22d2269d00b25e243ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 10:42:13 +0900 Subject: [PATCH 408/429] 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 b3842a528d..bcd5f9bd9a 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 fa2945db6a..5f0f3028f0 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 e35b1b5c42..59b026e0ad 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 1af684c4b2cb41e45f473e90285f5ac5cc192d12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 14:15:28 +0900 Subject: [PATCH 409/429] Revert "Fix editor tests failing due to empty files being specified" This reverts commit fdbd421040efcbd9718d95841034471f930e6291. --- .../Editing/TestSceneEditorBeatmapCreation.cs | 2 - osu.Game/Beatmaps/BeatmapManager.cs | 4 +- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + osu.Game/Tests/Visual/EditorTestScene.cs | 43 +------------------ 4 files changed, 5 insertions(+), 45 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index dd5e01adbb..7584c74c71 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -24,8 +24,6 @@ namespace osu.Game.Tests.Visual.Editing protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; - protected override bool IsolateSavingFromDatabase => false; - [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 18fbd1f5c2..f459657a0e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { var setInfo = info.BeatmapSet; @@ -282,7 +282,7 @@ namespace osu.Game.Beatmaps /// The beatmap to lookup. /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches /// A instance correlating to the provided . - public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 2717146c99..fa6dc5647d 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.Ruleset = ruleset; BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; + BeatmapInfo.BeatmapSet.Files = new List(); BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new BeatmapOnlineInfo(); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index de7fdd0cf8..a9ee8e2668 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -4,18 +4,11 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Platform; using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.IO.Archives; -using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; -using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -27,20 +20,10 @@ namespace osu.Game.Tests.Visual protected EditorClock EditorClock { get; private set; } - /// - /// Whether any saves performed by the editor should be isolate (and not persist) to the underlying . - /// - protected virtual bool IsolateSavingFromDatabase => true; - [BackgroundDependencyLoader] - private void load(GameHost host, AudioManager audio, RulesetStore rulesets) + private void load() { - var working = CreateWorkingBeatmap(Ruleset.Value); - - Beatmap.Value = working; - - if (IsolateSavingFromDatabase) - Dependencies.CacheAs(new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default, working)); + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); } protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true @@ -82,27 +65,5 @@ namespace osu.Game.Tests.Visual public new bool HasUnsavedChanges => base.HasUnsavedChanges; } - - private class TestBeatmapManager : BeatmapManager - { - private readonly WorkingBeatmap testBeatmap; - - public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap) - : base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, false) - { - this.testBeatmap = testBeatmap; - } - - protected override string ComputeHash(BeatmapSetInfo item, ArchiveReader reader = null) - => string.Empty; - - public override WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) - => testBeatmap; - - public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) - { - // don't actually care about saving for this context. - } - } } } From 9c4f39e9681644d84b60828ee8ce613b6d81a89f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 14:15:28 +0900 Subject: [PATCH 410/429] Revert "Make `BeatmapSetInfo.Files` non-nullable" This reverts commit c24712642cc567f158f2d4a7b559429567347d8b. --- osu.Game/Beatmaps/BeatmapManager.cs | 22 ++++++++++++---------- osu.Game/Beatmaps/BeatmapSetInfo.cs | 10 ++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f459657a0e..f70d29861f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -244,6 +245,8 @@ namespace osu.Game.Beatmaps { var setInfo = info.BeatmapSet; + Debug.Assert(setInfo.Files != null); + using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) @@ -284,21 +287,20 @@ namespace osu.Game.Beatmaps /// A instance correlating to the provided . public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { - if (beatmapInfo?.BeatmapSet == null) + if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) + return previous; + + if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - if (beatmapInfo == DefaultBeatmap?.BeatmapInfo) - return DefaultBeatmap; - - // if there are no files, presume the full beatmap info has not yet been fetched from the database. - if (beatmapInfo.BeatmapSet.Files.Count == 0) + if (beatmapInfo.BeatmapSet.Files == null) { - int lookupId = beatmapInfo.ID; - beatmapInfo = QueryBeatmap(b => b.ID == lookupId); + var info = beatmapInfo; + beatmapInfo = QueryBeatmap(b => b.ID == info.ID); } - if (beatmapInfo.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) - return previous; + if (beatmapInfo == null) + return DefaultBeatmap; lock (workingCache) { diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 3b1ff4ced0..1ce42535a0 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Testing; using osu.Game.Database; @@ -32,9 +31,6 @@ namespace osu.Game.Beatmaps public List Beatmaps { get; set; } - [NotNull] - public List Files { get; set; } = new List(); - [NotMapped] public BeatmapSetOnlineInfo OnlineInfo { get; set; } @@ -61,14 +57,16 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - public string StoryboardFile => Files.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. /// The path returned is relative to the user file storage. /// /// The name of the file to get the storage path of. - public string GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + public List Files { get; set; } public override string ToString() => Metadata?.ToString() ?? base.ToString(); From e52c0a34f82a6b93c740b4c2d3d068bc3c52196a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 14:31:09 +0900 Subject: [PATCH 411/429] Add temporary accounting for tests with null files --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f70d29861f..2e2bc96c21 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -293,7 +293,11 @@ namespace osu.Game.Beatmaps if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - if (beatmapInfo.BeatmapSet.Files == null) + // files may be null in some tests. + if (beatmapInfo.BeatmapSet?.Files == null) + return DefaultBeatmap; + + if (beatmapInfo.BeatmapSet.Files.Count == 0) { var info = beatmapInfo; beatmapInfo = QueryBeatmap(b => b.ID == info.ID); From 330d61862dba5abcd539f41997e614217de3f901 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 14:41:01 +0900 Subject: [PATCH 412/429] Add a mention of why `StartHidden` is required --- osu.Game/Overlays/Dialog/PopupDialog.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index c04ad7afb5..cd02900e88 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -95,6 +95,8 @@ namespace osu.Game.Overlays.Dialog } } + // We always want dialogs to show their appear animation, so we request they start hidden. + // Normally this would not be required, but is here due to the manual Show() call that occurs before LoadComplete(). protected override bool StartHidden => true; protected PopupDialog() From 1d5e8f4a912584428dee430764e49e5c6286a17b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 28 May 2021 17:19:36 +0900 Subject: [PATCH 413/429] Fix wrong/misleading comment of `InitialLifetimeOffset` --- .../Objects/Drawables/DrawableHitObject.cs | 15 +++------------ .../Rulesets/Objects/HitObjectLifetimeEntry.cs | 5 ++--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index cca55819c5..5faefbf469 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -445,9 +444,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. - /// The local drawable hierarchy is recursively delayed to for convenience. - /// - /// By default this will fade in the object from zero with no duration. + /// By default, this will fade in the object from zero with no duration. /// /// /// This is called once before every . This is to ensure a good state in the case @@ -623,17 +620,11 @@ namespace osu.Game.Rulesets.Objects.Drawables protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action); /// - /// A safe offset prior to the start time of at which this may begin displaying contents. + /// A offset prior to the start time of at which this may begin displaying contents. /// By default, s are assumed to display their contents within 10 seconds prior to the start time of . /// /// - /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. - /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). - /// - /// Only has an effect if this is not being pooled. - /// For pooled s, use instead. - /// + /// The initial transformation () starts at this offset before the start time of . /// protected virtual double InitialLifetimeOffset => 10000; diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 0d1eb68f07..cf62226d94 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -82,9 +82,8 @@ namespace osu.Game.Rulesets.Objects /// By default, s are assumed to display their contents within 10 seconds prior to their start time. /// /// - /// This is only used as an optimisation to delay the initial update of the and may be tuned more aggressively if required. - /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// This is only used as an optimisation to delay the initial application of the to a . + /// A more accurate should be set on the hit object application, for further optimisation. /// protected virtual double InitialLifetimeOffset => 10000; From ee5eb9576fc1f47c6d250b141d8f496ca2113258 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 18:10:27 +0900 Subject: [PATCH 414/429] Fix completely wrong conditional logic --- osu.Game/Beatmaps/BeatmapManager.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2e2bc96c21..23810367a1 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -293,11 +293,9 @@ namespace osu.Game.Beatmaps if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - // files may be null in some tests. - if (beatmapInfo.BeatmapSet?.Files == null) - return DefaultBeatmap; - - if (beatmapInfo.BeatmapSet.Files.Count == 0) + // force a re-query if files are not in a state which looks like the model has + // full database information present. + if (beatmapInfo.BeatmapSet.Files == null || beatmapInfo.BeatmapSet.Files.Count == 0) { var info = beatmapInfo; beatmapInfo = QueryBeatmap(b => b.ID == info.ID); From ba80361c4c5483f3981aa9103d365e8f3b74a27b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 May 2021 18:37:33 +0900 Subject: [PATCH 415/429] Fix tests that were previously doing reference comparisons --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 643f4131dc..2eb6d3f80e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -358,7 +358,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target)); // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target); + AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target)); } [Test] @@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0); // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target); + AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target)); } [Test] @@ -781,7 +781,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); - AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo == groupIcon.Items.First().Beatmap); + AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().Beatmap)); } [Test] From 43bf734816db2051dc88b351f8be086a47a6734c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 28 May 2021 21:26:21 +0900 Subject: [PATCH 416/429] Reset lifetime on `HitObject.DefaultsApplied` --- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 0d1eb68f07..4e11c9aeee 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -35,7 +35,9 @@ namespace osu.Game.Rulesets.Objects HitObject = hitObject; startTimeBindable.BindTo(HitObject.StartTimeBindable); - startTimeBindable.BindValueChanged(onStartTimeChanged, true); + startTimeBindable.BindValueChanged(_ => setInitialLifetime(), true); + + HitObject.DefaultsApplied += _ => setInitialLifetime(); } // The lifetime, as set by the hitobject. @@ -89,8 +91,8 @@ namespace osu.Game.Rulesets.Objects protected virtual double InitialLifetimeOffset => 10000; /// - /// Resets according to the change in start time of the . + /// Set using . /// - private void onStartTimeChanged(ValueChangedEvent startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + private void setInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; } } From 68c5f4813e6c0e5fa0964761b5be360236964d47 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 28 May 2021 21:27:10 +0900 Subject: [PATCH 417/429] Add test of lifetime update on DefaultsApplied --- osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 0bec02c488..5f99b886fb 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -3,6 +3,8 @@ using NUnit.Framework; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Tests.Visual; @@ -70,6 +72,15 @@ namespace osu.Game.Tests.Gameplay AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000); } + [Test] + public void TestLifetimeUpdatedOnDefaultApplied() + { + TestLifetimeEntry entry = null; + AddStep("Create entry", () => entry = new TestLifetimeEntry(new HitObject()) { LifetimeStart = 1 }); + AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty())); + AddAssert("Lifetime is updated", () => entry.LifetimeStart == -TestLifetimeEntry.INITIAL_LIFETIME_OFFSET); + } + private class TestDrawableHitObject : DrawableHitObject { public const double INITIAL_LIFETIME_OFFSET = 100; From 265dfe54160221e05f2a5577fc10a21f7440d6b2 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 28 May 2021 21:51:48 +0900 Subject: [PATCH 418/429] Add test case of DHO setting LifetimeStart in OnApply --- .../Gameplay/TestSceneDrawableHitObject.cs | 22 +++++++++++++++++++ .../Objects/HitObjectLifetimeEntry.cs | 2 ++ 2 files changed, 24 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 5f99b886fb..3de2dc72bb 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -79,17 +79,39 @@ namespace osu.Game.Tests.Gameplay AddStep("Create entry", () => entry = new TestLifetimeEntry(new HitObject()) { LifetimeStart = 1 }); AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty())); AddAssert("Lifetime is updated", () => entry.LifetimeStart == -TestLifetimeEntry.INITIAL_LIFETIME_OFFSET); + + TestDrawableHitObject dho = null; + AddStep("Create DHO", () => + { + dho = new TestDrawableHitObject(null); + dho.Apply(entry); + Child = dho; + dho.SetLifetimeStartOnApply = true; + }); + AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty())); + AddAssert("Lifetime is correct", () => dho.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY && entry.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY); } private class TestDrawableHitObject : DrawableHitObject { public const double INITIAL_LIFETIME_OFFSET = 100; + public const double LIFETIME_ON_APPLY = 222; protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET; + public bool SetLifetimeStartOnApply; + public TestDrawableHitObject(HitObject hitObject) : base(hitObject) { } + + protected override void OnApply() + { + base.OnApply(); + + if (SetLifetimeStartOnApply) + LifetimeStart = LIFETIME_ON_APPLY; + } } private class TestLifetimeEntry : HitObjectLifetimeEntry diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 4e11c9aeee..3375f41cc3 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Objects startTimeBindable.BindTo(HitObject.StartTimeBindable); startTimeBindable.BindValueChanged(_ => setInitialLifetime(), true); + // It is important to subscribe to this event before applied to a DrawableHitObject. + // Otherwise DHO cannot overwrite LifetimeStart set in setInitialLifetime. HitObject.DefaultsApplied += _ => setInitialLifetime(); } From 2439de1c3dd3ce3ff085d41c6c1aa95e2cf1d1e2 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 28 May 2021 18:40:58 +0200 Subject: [PATCH 419/429] fix capitalzation of osu!catch HitResults --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index e3c457693e..23ce444560 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -161,13 +161,13 @@ namespace osu.Game.Rulesets.Catch switch (result) { case HitResult.LargeTickHit: - return "large droplet"; + return "Large droplet"; case HitResult.SmallTickHit: - return "small droplet"; + return "Small droplet"; case HitResult.LargeBonus: - return "banana"; + return "Banana"; } return base.GetDisplayNameForHitResult(result); From 951fc5ef6ecff428a20b7b52c8f26cab9c170fe9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 May 2021 15:39:12 +0900 Subject: [PATCH 420/429] Remove misleading comment and misplaced bug fix This shouldn't be fixed in a test scene; the underlying issue should be fixed in actual game code. --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index bece80903f..d6bedffaa8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; using osu.Framework.Testing; @@ -117,18 +116,6 @@ namespace osu.Game.Tests.Visual.Gameplay : base(source) { } - - public override Drawable GetDrawableComponent(ISkinComponent component) - { - var drawable = base.GetDrawableComponent(component); - if (drawable != null) - return drawable; - - // this isn't really supposed to make a difference from returning null, - // but it appears it does, returning null will skip over falling back to beatmap skin, - // while calling Source.GetDrawableComponent() doesn't. - return Source.GetDrawableComponent(component); - } } } } From 200fecc774d7e0e79b63dc3267e339a976cc58f4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 May 2021 21:22:39 +0300 Subject: [PATCH 421/429] Fix missing using directive --- osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 31d29e6807..cc53e50884 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics.Containers; From 53cbf369d7a55e1c6ef5f38af9d35de8ce4739ae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 May 2021 21:22:46 +0300 Subject: [PATCH 422/429] Fix potential nullref --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 04b2346c6a..caf37e5bc9 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -71,6 +71,6 @@ namespace osu.Game.Skinning } private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => - new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; + new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString }; } } From fbc316ea1d6cf401b0a3cf75ecb604b51f1c2211 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 27 May 2021 05:15:29 +0300 Subject: [PATCH 423/429] Fix legacy skin transformers potentially ignoring source implementations --- .../Legacy/CatchLegacySkinTransformer.cs | 20 +++++++++++-------- .../Legacy/ManiaLegacySkinTransformer.cs | 8 +++++--- .../Legacy/OsuLegacySkinTransformer.cs | 7 ++++--- .../Legacy/TaikoLegacySkinTransformer.cs | 7 ++++--- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 1b48832ed6..6efd9e88c4 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -28,12 +28,15 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy { case HUDSkinComponents.ComboCounter: // catch may provide its own combo counter; hide the default. - return providesComboCounter ? Drawable.Empty() : null; + if (providesComboCounter) + return Drawable.Empty(); + + break; } } if (!(component is CatchSkinComponent catchSkinComponent)) - return null; + return Source.GetDrawableComponent(component); switch (catchSkinComponent.Component) { @@ -41,19 +44,19 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy if (GetTexture("fruit-pear") != null) return new LegacyFruitPiece(); - break; + return null; case CatchSkinComponents.Banana: if (GetTexture("fruit-bananas") != null) return new LegacyBananaPiece(); - break; + return null; case CatchSkinComponents.Droplet: if (GetTexture("fruit-drop") != null) return new LegacyDropletPiece(); - break; + return null; case CatchSkinComponents.CatcherIdle: return this.GetAnimation("fruit-catcher-idle", true, true, true) ?? @@ -71,10 +74,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy if (providesComboCounter) return new LegacyCatchComboCounter(Source); - break; - } + return null; - return null; + default: + return Source.GetDrawableComponent(component); + } } public override IBindable GetConfig(TLookup lookup) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 24ccae895d..6617fcefe7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -120,12 +120,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy case ManiaSkinComponents.StageForeground: return new LegacyStageForeground(); + + default: + return Source.GetDrawableComponent(component); } - break; + default: + return Source.GetDrawableComponent(component); } - - return null; } private Drawable getResult(HitResult result) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 88302ebc57..f1376551f5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public override Drawable GetDrawableComponent(ISkinComponent component) { if (!(component is OsuSkinComponent osuComponent)) - return null; + return Source.GetDrawableComponent(component); switch (osuComponent.Component) { @@ -115,9 +115,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return new LegacyOldStyleSpinner(); return null; - } - return null; + default: + return Source.GetDrawableComponent(component); + } } public override IBindable GetConfig(TLookup lookup) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 3e506f69ce..05778a80ef 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } if (!(component is TaikoSkinComponent taikoComponent)) - return null; + return Source.GetDrawableComponent(component); switch (taikoComponent.Component) { @@ -130,9 +130,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy case TaikoSkinComponents.Mascot: return new DrawableTaikoMascot(); - } - return Source.GetDrawableComponent(component); + default: + return Source.GetDrawableComponent(component); + } } private string getHitName(TaikoSkinComponents component) From abb77b95b4c1e3e8a28f4c7b40b320c5ee459bc1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 30 May 2021 13:06:28 +0900 Subject: [PATCH 424/429] Fix comment grammar --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5faefbf469..6c688c1625 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -620,7 +620,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action); /// - /// A offset prior to the start time of at which this may begin displaying contents. + /// An offset prior to the start time of at which this may begin displaying contents. /// By default, s are assumed to display their contents within 10 seconds prior to the start time of . /// /// From 50d71faf565ea5f11df4d9192379de33d240e5f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 May 2021 17:55:10 +0900 Subject: [PATCH 425/429] Restructure lookup code to avoid repeating the base call --- .../Legacy/CatchLegacySkinTransformer.cs | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 6efd9e88c4..e43d88ad40 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -35,50 +35,49 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy } } - if (!(component is CatchSkinComponent catchSkinComponent)) - return Source.GetDrawableComponent(component); - - switch (catchSkinComponent.Component) + if (component is CatchSkinComponent catchSkinComponent) { - case CatchSkinComponents.Fruit: - if (GetTexture("fruit-pear") != null) - return new LegacyFruitPiece(); + switch (catchSkinComponent.Component) + { + case CatchSkinComponents.Fruit: + if (GetTexture("fruit-pear") != null) + return new LegacyFruitPiece(); - return null; + return null; - case CatchSkinComponents.Banana: - if (GetTexture("fruit-bananas") != null) - return new LegacyBananaPiece(); + case CatchSkinComponents.Banana: + if (GetTexture("fruit-bananas") != null) + return new LegacyBananaPiece(); - return null; + return null; - case CatchSkinComponents.Droplet: - if (GetTexture("fruit-drop") != null) - return new LegacyDropletPiece(); + case CatchSkinComponents.Droplet: + if (GetTexture("fruit-drop") != null) + return new LegacyDropletPiece(); - return null; + return null; - case CatchSkinComponents.CatcherIdle: - return this.GetAnimation("fruit-catcher-idle", true, true, true) ?? - this.GetAnimation("fruit-ryuuta", true, true, true); + case CatchSkinComponents.CatcherIdle: + return this.GetAnimation("fruit-catcher-idle", true, true, true) ?? + this.GetAnimation("fruit-ryuuta", true, true, true); - case CatchSkinComponents.CatcherFail: - return this.GetAnimation("fruit-catcher-fail", true, true, true) ?? - this.GetAnimation("fruit-ryuuta", true, true, true); + case CatchSkinComponents.CatcherFail: + return this.GetAnimation("fruit-catcher-fail", true, true, true) ?? + this.GetAnimation("fruit-ryuuta", true, true, true); - case CatchSkinComponents.CatcherKiai: - return this.GetAnimation("fruit-catcher-kiai", true, true, true) ?? - this.GetAnimation("fruit-ryuuta", true, true, true); + case CatchSkinComponents.CatcherKiai: + return this.GetAnimation("fruit-catcher-kiai", true, true, true) ?? + this.GetAnimation("fruit-ryuuta", true, true, true); - case CatchSkinComponents.CatchComboCounter: - if (providesComboCounter) - return new LegacyCatchComboCounter(Source); + case CatchSkinComponents.CatchComboCounter: + if (providesComboCounter) + return new LegacyCatchComboCounter(Source); - return null; - - default: - return Source.GetDrawableComponent(component); + return null; + } } + + return Source.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) From 17574833fb83dd3f71bcf86acb22b3d44c0603a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 May 2021 19:15:59 +0900 Subject: [PATCH 426/429] Update other transformers with similar refactored logic --- .../Legacy/ManiaLegacySkinTransformer.cs | 8 +- .../Legacy/OsuLegacySkinTransformer.cs | 143 +++++++++--------- .../Legacy/TaikoLegacySkinTransformer.cs | 131 ++++++++-------- 3 files changed, 139 insertions(+), 143 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 6617fcefe7..261b8b1fad 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -120,14 +120,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy case ManiaSkinComponents.StageForeground: return new LegacyStageForeground(); - - default: - return Source.GetDrawableComponent(component); } - default: - return Source.GetDrawableComponent(component); + break; } + + return Source.GetDrawableComponent(component); } private Drawable getResult(HitResult result) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index f1376551f5..ffd4f78400 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -34,91 +34,90 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public override Drawable GetDrawableComponent(ISkinComponent component) { - if (!(component is OsuSkinComponent osuComponent)) - return Source.GetDrawableComponent(component); - - switch (osuComponent.Component) + if (component is OsuSkinComponent osuComponent) { - case OsuSkinComponents.FollowPoint: - return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false); + switch (osuComponent.Component) + { + case OsuSkinComponents.FollowPoint: + return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false); - case OsuSkinComponents.SliderFollowCircle: - var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); - if (followCircle != null) - // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x - followCircle.Scale *= 0.5f; - return followCircle; + case OsuSkinComponents.SliderFollowCircle: + var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); + if (followCircle != null) + // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x + followCircle.Scale *= 0.5f; + return followCircle; - case OsuSkinComponents.SliderBall: - var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); + case OsuSkinComponents.SliderBall: + var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); - // todo: slider ball has a custom frame delay based on velocity - // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); + // todo: slider ball has a custom frame delay based on velocity + // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); - if (sliderBallContent != null) - return new LegacySliderBall(sliderBallContent); + if (sliderBallContent != null) + return new LegacySliderBall(sliderBallContent); - return null; - - case OsuSkinComponents.SliderBody: - if (hasHitCircle.Value) - return new LegacySliderBody(); - - return null; - - case OsuSkinComponents.SliderTailHitCircle: - if (hasHitCircle.Value) - return new LegacyMainCirclePiece("sliderendcircle", false); - - return null; - - case OsuSkinComponents.SliderHeadHitCircle: - if (hasHitCircle.Value) - return new LegacyMainCirclePiece("sliderstartcircle"); - - return null; - - case OsuSkinComponents.HitCircle: - if (hasHitCircle.Value) - return new LegacyMainCirclePiece(); - - return null; - - case OsuSkinComponents.Cursor: - if (Source.GetTexture("cursor") != null) - return new LegacyCursor(); - - return null; - - case OsuSkinComponents.CursorTrail: - if (Source.GetTexture("cursortrail") != null) - return new LegacyCursorTrail(); - - return null; - - case OsuSkinComponents.HitCircleText: - if (!this.HasFont(LegacyFont.HitCircle)) return null; - return new LegacySpriteText(LegacyFont.HitCircle) - { - // stable applies a blanket 0.8x scale to hitcircle fonts - Scale = new Vector2(0.8f), - }; + case OsuSkinComponents.SliderBody: + if (hasHitCircle.Value) + return new LegacySliderBody(); - case OsuSkinComponents.SpinnerBody: - bool hasBackground = Source.GetTexture("spinner-background") != null; + return null; - if (Source.GetTexture("spinner-top") != null && !hasBackground) - return new LegacyNewStyleSpinner(); - else if (hasBackground) - return new LegacyOldStyleSpinner(); + case OsuSkinComponents.SliderTailHitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece("sliderendcircle", false); - return null; + return null; - default: - return Source.GetDrawableComponent(component); + case OsuSkinComponents.SliderHeadHitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece("sliderstartcircle"); + + return null; + + case OsuSkinComponents.HitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece(); + + return null; + + case OsuSkinComponents.Cursor: + if (Source.GetTexture("cursor") != null) + return new LegacyCursor(); + + return null; + + case OsuSkinComponents.CursorTrail: + if (Source.GetTexture("cursortrail") != null) + return new LegacyCursorTrail(); + + return null; + + case OsuSkinComponents.HitCircleText: + if (!this.HasFont(LegacyFont.HitCircle)) + return null; + + return new LegacySpriteText(LegacyFont.HitCircle) + { + // stable applies a blanket 0.8x scale to hitcircle fonts + Scale = new Vector2(0.8f), + }; + + case OsuSkinComponents.SpinnerBody: + bool hasBackground = Source.GetTexture("spinner-background") != null; + + if (Source.GetTexture("spinner-top") != null && !hasBackground) + return new LegacyNewStyleSpinner(); + else if (hasBackground) + return new LegacyOldStyleSpinner(); + + return null; + } } + + return Source.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 05778a80ef..e0557c8617 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -38,102 +38,101 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy return Drawable.Empty().With(d => d.Expire()); } - if (!(component is TaikoSkinComponent taikoComponent)) - return Source.GetDrawableComponent(component); - - switch (taikoComponent.Component) + if (component is TaikoSkinComponent taikoComponent) { - case TaikoSkinComponents.DrumRollBody: - if (GetTexture("taiko-roll-middle") != null) - return new LegacyDrumRoll(); + switch (taikoComponent.Component) + { + case TaikoSkinComponents.DrumRollBody: + if (GetTexture("taiko-roll-middle") != null) + return new LegacyDrumRoll(); - return null; + return null; - case TaikoSkinComponents.InputDrum: - if (GetTexture("taiko-bar-left") != null) - return new LegacyInputDrum(); + case TaikoSkinComponents.InputDrum: + if (GetTexture("taiko-bar-left") != null) + return new LegacyInputDrum(); - return null; + return null; - case TaikoSkinComponents.CentreHit: - case TaikoSkinComponents.RimHit: + case TaikoSkinComponents.CentreHit: + case TaikoSkinComponents.RimHit: - if (GetTexture("taikohitcircle") != null) - return new LegacyHit(taikoComponent.Component); + if (GetTexture("taikohitcircle") != null) + return new LegacyHit(taikoComponent.Component); - return null; + return null; - case TaikoSkinComponents.DrumRollTick: - return this.GetAnimation("sliderscorepoint", false, false); + case TaikoSkinComponents.DrumRollTick: + return this.GetAnimation("sliderscorepoint", false, false); - case TaikoSkinComponents.HitTarget: - if (GetTexture("taikobigcircle") != null) - return new TaikoLegacyHitTarget(); + case TaikoSkinComponents.HitTarget: + if (GetTexture("taikobigcircle") != null) + return new TaikoLegacyHitTarget(); - return null; + return null; - case TaikoSkinComponents.PlayfieldBackgroundRight: - if (GetTexture("taiko-bar-right") != null) - return new TaikoLegacyPlayfieldBackgroundRight(); + case TaikoSkinComponents.PlayfieldBackgroundRight: + if (GetTexture("taiko-bar-right") != null) + return new TaikoLegacyPlayfieldBackgroundRight(); - return null; + return null; - case TaikoSkinComponents.PlayfieldBackgroundLeft: - // This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins). - if (GetTexture("taiko-bar-right") != null) - return Drawable.Empty(); + case TaikoSkinComponents.PlayfieldBackgroundLeft: + // This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins). + if (GetTexture("taiko-bar-right") != null) + return Drawable.Empty(); - return null; + return null; - case TaikoSkinComponents.BarLine: - if (GetTexture("taiko-barline") != null) - return new LegacyBarLine(); + case TaikoSkinComponents.BarLine: + if (GetTexture("taiko-barline") != null) + return new LegacyBarLine(); - return null; + return null; - case TaikoSkinComponents.TaikoExplosionMiss: + case TaikoSkinComponents.TaikoExplosionMiss: - var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); - if (missSprite != null) - return new LegacyHitExplosion(missSprite); + var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); + if (missSprite != null) + return new LegacyHitExplosion(missSprite); - return null; + return null; - case TaikoSkinComponents.TaikoExplosionOk: - case TaikoSkinComponents.TaikoExplosionGreat: + case TaikoSkinComponents.TaikoExplosionOk: + case TaikoSkinComponents.TaikoExplosionGreat: - var hitName = getHitName(taikoComponent.Component); - var hitSprite = this.GetAnimation(hitName, true, false); + var hitName = getHitName(taikoComponent.Component); + var hitSprite = this.GetAnimation(hitName, true, false); - if (hitSprite != null) - { - var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); + if (hitSprite != null) + { + var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); - return new LegacyHitExplosion(hitSprite, strongHitSprite); - } + return new LegacyHitExplosion(hitSprite, strongHitSprite); + } - return null; + return null; - case TaikoSkinComponents.TaikoExplosionKiai: - // suppress the default kiai explosion if the skin brings its own sprites. - // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. - if (hasExplosion.Value) - return Drawable.Empty().With(d => d.Expire()); + case TaikoSkinComponents.TaikoExplosionKiai: + // suppress the default kiai explosion if the skin brings its own sprites. + // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. + if (hasExplosion.Value) + return Drawable.Empty().With(d => d.Expire()); - return null; + return null; - case TaikoSkinComponents.Scroller: - if (GetTexture("taiko-slider") != null) - return new LegacyTaikoScroller(); + case TaikoSkinComponents.Scroller: + if (GetTexture("taiko-slider") != null) + return new LegacyTaikoScroller(); - return null; + return null; - case TaikoSkinComponents.Mascot: - return new DrawableTaikoMascot(); - - default: - return Source.GetDrawableComponent(component); + case TaikoSkinComponents.Mascot: + return new DrawableTaikoMascot(); + } } + + return Source.GetDrawableComponent(component); } private string getHitName(TaikoSkinComponents component) From bca8a9ea53890906a806e8f97764147bd5d2098f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 May 2021 10:02:02 +0900 Subject: [PATCH 427/429] Fix HandleFrame crashing when not playing --- osu.Game/Online/Spectator/SpectatorClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index a4fc963328..2546374b21 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -235,6 +235,9 @@ namespace osu.Game.Online.Spectator { Debug.Assert(ThreadSafety.IsUpdateThread); + if (!IsPlaying) + return; + if (frame is IConvertibleReplayFrame convertible) pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); From a4dca6f839d7973372b78023e8133b8e5f8e993f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 May 2021 13:39:18 +0900 Subject: [PATCH 428/429] Reorder methods around load() --- osu.Game/OsuGameBase.cs | 82 ++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dfb9bd3a99..918f231a19 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -160,47 +160,6 @@ namespace osu.Game Name = @"osu!lazer"; } - public override void SetHost(GameHost host) - { - base.SetHost(host); - - // may be non-null for certain tests - Storage ??= host.Storage; - - LocalConfig ??= UseDevelopmentServer - ? new DevelopmentOsuConfigManager(Storage) - : new OsuConfigManager(Storage); - } - - /// - /// Use to programatically exit the game as if the user was triggering via alt-f4. - /// Will keep persisting until an exit occurs (exit may be blocked multiple times). - /// - public void GracefullyExit() - { - if (!OnExiting()) - Exit(); - else - Scheduler.AddDelayed(GracefullyExit, 2000); - } - - public void Migrate(string path) - { - contextFactory.FlushConnections(); - (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); - } - - protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); - - protected virtual BatteryInfo CreateBatteryInfo() => null; - - protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer(); - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); - [BackgroundDependencyLoader] private void load() { @@ -390,6 +349,47 @@ namespace osu.Game FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + public override void SetHost(GameHost host) + { + base.SetHost(host); + + // may be non-null for certain tests + Storage ??= host.Storage; + + LocalConfig ??= UseDevelopmentServer + ? new DevelopmentOsuConfigManager(Storage) + : new OsuConfigManager(Storage); + } + + /// + /// Use to programatically exit the game as if the user was triggering via alt-f4. + /// Will keep persisting until an exit occurs (exit may be blocked multiple times). + /// + public void GracefullyExit() + { + if (!OnExiting()) + Exit(); + else + Scheduler.AddDelayed(GracefullyExit, 2000); + } + + public void Migrate(string path) + { + contextFactory.FlushConnections(); + (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + } + + protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); + + protected virtual BatteryInfo CreateBatteryInfo() => null; + + protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer(); + + protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + private void onRulesetChanged(ValueChangedEvent r) { var dict = new Dictionary>(); From 0c101d285925cfd1e392dc383f8186a62a07a9a9 Mon Sep 17 00:00:00 2001 From: ekrctb <32995012+ekrctb@users.noreply.github.com> Date: Mon, 31 May 2021 13:41:49 +0900 Subject: [PATCH 429/429] Apply comment rewording suggestion Co-authored-by: Dan Balasescu --- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 3375f41cc3..c275ba32f6 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -37,8 +37,8 @@ namespace osu.Game.Rulesets.Objects startTimeBindable.BindTo(HitObject.StartTimeBindable); startTimeBindable.BindValueChanged(_ => setInitialLifetime(), true); - // It is important to subscribe to this event before applied to a DrawableHitObject. - // Otherwise DHO cannot overwrite LifetimeStart set in setInitialLifetime. + // Subscribe to this event before the DrawableHitObject so that the local callback is invoked before the entry is re-applied as a result of DefaultsApplied. + // This way, the DrawableHitObject can use OnApply() to overwrite the LifetimeStart that was set inside setInitialLifetime(). HitObject.DefaultsApplied += _ => setInitialLifetime(); }