From 321815f535b53fce4f4db3749800422d55ef8ba7 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Thu, 9 Jul 2020 14:01:28 -0500 Subject: [PATCH 001/322] Add playfield bounds box with toggle and dim slider --- osu.Game/Configuration/OsuConfigManager.cs | 7 +++- .../Sections/Gameplay/GeneralSettings.cs | 14 ++++++- osu.Game/Rulesets/UI/Playfield.cs | 41 ++++++++++++++++++- .../Play/PlayerSettings/VisualSettings.cs | 15 ++++++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9d31bc9bba..40a132a8e8 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -99,6 +99,9 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); + Set(OsuSetting.ShowPlayfieldArea, false); + Set(OsuSetting.PlayfieldAreaDimLevel, 0.1, 0, 1, 0.01); + // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -227,6 +230,8 @@ namespace osu.Game.Configuration IntroSequence, UIHoldActivationDelay, HitLighting, - MenuBackgroundSource + MenuBackgroundSource, + ShowPlayfieldArea, + PlayfieldAreaDimLevel } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 93a02ea0e4..ad02b54dd8 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -76,7 +76,19 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Score display mode", Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) - } + }, + new SettingsCheckbox + { + LabelText = "Show playfield area", + Bindable = config.GetBindable(OsuSetting.ShowPlayfieldArea) + }, + new SettingsSlider + { + LabelText = "Playfield area dim", + Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel), + KeyboardStep = 0.01f, + DisplayAsPercentage = true + }, }; } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index c52183f3f2..2ec84cca8c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,6 +12,9 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osuTK; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; +using osu.Game.Configuration; namespace osu.Game.Rulesets.UI { @@ -51,6 +54,10 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool DisplayJudgements = new BindableBool(true); + private Bindable showPlayfieldArea; + private Bindable playfieldAreaDimLevel; + private Box playfieldArea; + /// /// Creates a new . /// @@ -65,7 +72,7 @@ namespace osu.Game.Rulesets.UI private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { Cursor = CreateCursor(); @@ -76,6 +83,38 @@ namespace osu.Game.Rulesets.UI AddInternal(Cursor); } + + showPlayfieldArea = config.GetBindable(OsuSetting.ShowPlayfieldArea); + playfieldAreaDimLevel = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); + showPlayfieldArea.ValueChanged += _ => UpdateVisuals(); + playfieldAreaDimLevel.ValueChanged += _ => UpdateVisuals(); + UpdateVisuals(); + } + protected virtual void UpdateVisuals() + { + if(playfieldArea == null) + { + if (showPlayfieldArea.Value) + { + AddInternal(playfieldArea = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = (float)playfieldAreaDimLevel.Value, + }); + } + } + else + { + if (showPlayfieldArea.Value) + { + playfieldArea.Alpha = (float)playfieldAreaDimLevel.Value; + } + else + { + playfieldArea.Alpha = 0; + } + } } /// diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index d6c66d0751..36e7c53132 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -14,9 +14,11 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; + private readonly PlayerSliderBar playfieldAreaDimSliderBar; private readonly PlayerCheckbox showStoryboardToggle; private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapHitsoundsToggle; + private readonly PlayerCheckbox showPlayfieldAreaToggle; public VisualSettings() { @@ -39,12 +41,21 @@ namespace osu.Game.Screens.Play.PlayerSettings DisplayAsPercentage = true }, new OsuSpriteText + { + Text = "Playfieldd area dim:" + }, + playfieldAreaDimSliderBar = new PlayerSliderBar + { + DisplayAsPercentage = true + }, + new OsuSpriteText { Text = "Toggles:" }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" }, + showPlayfieldAreaToggle = new PlayerCheckbox { LabelText = "Show playfield area" } }; } @@ -53,9 +64,11 @@ namespace osu.Game.Screens.Play.PlayerSettings { dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); + playfieldAreaDimSliderBar.Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); + showPlayfieldAreaToggle.Current = config.GetBindable(OsuSetting.ShowPlayfieldArea); } } } From 8121ccaad077e5a93088185ea0f92036af7ca6a1 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Thu, 9 Jul 2020 15:00:26 -0500 Subject: [PATCH 002/322] Change Box to EditorPlayfieldBorder --- osu.Game/Rulesets/UI/Playfield.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 2ec84cca8c..f6eb74a030 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,9 +12,8 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osuTK; -using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; using osu.Game.Configuration; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.UI { @@ -56,7 +55,7 @@ namespace osu.Game.Rulesets.UI private Bindable showPlayfieldArea; private Bindable playfieldAreaDimLevel; - private Box playfieldArea; + private EditorPlayfieldBorder playfieldArea; /// /// Creates a new . @@ -90,16 +89,16 @@ namespace osu.Game.Rulesets.UI playfieldAreaDimLevel.ValueChanged += _ => UpdateVisuals(); UpdateVisuals(); } + protected virtual void UpdateVisuals() { - if(playfieldArea == null) + if (playfieldArea == null) { if (showPlayfieldArea.Value) { - AddInternal(playfieldArea = new Box + AddInternal(playfieldArea = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, Alpha = (float)playfieldAreaDimLevel.Value, }); } From 0d95b768aa2e04f8543731afa71a31282c4c42c3 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Fri, 10 Jul 2020 07:34:48 -0500 Subject: [PATCH 003/322] Rename and Move EditorPlayfieldBorder to PlayfieldBorder for general purpose --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +- osu.Game/Rulesets/UI/Playfield.cs | 40 +------------------ ...rPlayfieldBorder.cs => PlayfieldBorder.cs} | 6 +-- 3 files changed, 6 insertions(+), 43 deletions(-) rename osu.Game/Screens/{Edit/Compose/Components/EditorPlayfieldBorder.cs => PlayfieldBorder.cs} (82%) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c25fb03fd0..6028ab77e1 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose; @@ -108,7 +109,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] { LayerBelowRuleset, - new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } + new PlayfieldBorder { RelativeSizeAxes = Axes.Both } }), drawableRulesetWrapper, // layers above playfield diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index f6eb74a030..c52183f3f2 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,8 +12,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osuTK; -using osu.Game.Configuration; -using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.UI { @@ -53,10 +51,6 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool DisplayJudgements = new BindableBool(true); - private Bindable showPlayfieldArea; - private Bindable playfieldAreaDimLevel; - private EditorPlayfieldBorder playfieldArea; - /// /// Creates a new . /// @@ -71,7 +65,7 @@ namespace osu.Game.Rulesets.UI private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { Cursor = CreateCursor(); @@ -82,38 +76,6 @@ namespace osu.Game.Rulesets.UI AddInternal(Cursor); } - - showPlayfieldArea = config.GetBindable(OsuSetting.ShowPlayfieldArea); - playfieldAreaDimLevel = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); - showPlayfieldArea.ValueChanged += _ => UpdateVisuals(); - playfieldAreaDimLevel.ValueChanged += _ => UpdateVisuals(); - UpdateVisuals(); - } - - protected virtual void UpdateVisuals() - { - if (playfieldArea == null) - { - if (showPlayfieldArea.Value) - { - AddInternal(playfieldArea = new EditorPlayfieldBorder - { - RelativeSizeAxes = Axes.Both, - Alpha = (float)playfieldAreaDimLevel.Value, - }); - } - } - else - { - if (showPlayfieldArea.Value) - { - playfieldArea.Alpha = (float)playfieldAreaDimLevel.Value; - } - else - { - playfieldArea.Alpha = 0; - } - } } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs b/osu.Game/Screens/PlayfieldBorder.cs similarity index 82% rename from osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs rename to osu.Game/Screens/PlayfieldBorder.cs index 4d956336b7..a3be38f0a2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs +++ b/osu.Game/Screens/PlayfieldBorder.cs @@ -6,14 +6,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Compose.Components +namespace osu.Game.Screens { /// /// Provides a border around the playfield. /// - public class EditorPlayfieldBorder : CompositeDrawable + public class PlayfieldBorder : CompositeDrawable { - public EditorPlayfieldBorder() + public PlayfieldBorder() { RelativeSizeAxes = Axes.Both; From d40f209f4bbb15406b5512ad77585311ead25af4 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Fri, 10 Jul 2020 07:36:21 -0500 Subject: [PATCH 004/322] Move Playfield Border to OsuPlayfield Ruleset --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 19 +++++++++++++++++++ osu.Game/Configuration/OsuConfigManager.cs | 6 ++---- .../Sections/Gameplay/GeneralSettings.cs | 13 +++---------- .../Play/PlayerSettings/VisualSettings.cs | 15 +-------------- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 4b1a2ce43c..3189db69a5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -12,6 +12,10 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; +using osu.Framework.Allocation; +using osu.Game.Configuration; +using osu.Framework.Bindables; +using osu.Game.Screens; namespace osu.Game.Rulesets.Osu.UI { @@ -26,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); + private Bindable showPlayfieldBorder; + public OsuPlayfield() { InternalChildren = new Drawable[] @@ -56,6 +62,19 @@ namespace osu.Game.Rulesets.Osu.UI hitPolicy = new OrderedHitPolicy(HitObjectContainer); } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); + if (showPlayfieldBorder.Value) + { + AddInternal(new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both + }); + } + } + public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 40a132a8e8..9ed73b7bd6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -99,8 +99,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); - Set(OsuSetting.ShowPlayfieldArea, false); - Set(OsuSetting.PlayfieldAreaDimLevel, 0.1, 0, 1, 0.01); + Set(OsuSetting.ShowPlayfieldBorder, false); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -231,7 +230,6 @@ namespace osu.Game.Configuration UIHoldActivationDelay, HitLighting, MenuBackgroundSource, - ShowPlayfieldArea, - PlayfieldAreaDimLevel + ShowPlayfieldBorder } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index ad02b54dd8..85eb61edff 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -79,16 +79,9 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }, new SettingsCheckbox { - LabelText = "Show playfield area", - Bindable = config.GetBindable(OsuSetting.ShowPlayfieldArea) - }, - new SettingsSlider - { - LabelText = "Playfield area dim", - Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel), - KeyboardStep = 0.01f, - DisplayAsPercentage = true - }, + LabelText = "Show playfield border", + Bindable = config.GetBindable(OsuSetting.ShowPlayfieldBorder) + } }; } } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 36e7c53132..d6c66d0751 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -14,11 +14,9 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; - private readonly PlayerSliderBar playfieldAreaDimSliderBar; private readonly PlayerCheckbox showStoryboardToggle; private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapHitsoundsToggle; - private readonly PlayerCheckbox showPlayfieldAreaToggle; public VisualSettings() { @@ -41,21 +39,12 @@ namespace osu.Game.Screens.Play.PlayerSettings DisplayAsPercentage = true }, new OsuSpriteText - { - Text = "Playfieldd area dim:" - }, - playfieldAreaDimSliderBar = new PlayerSliderBar - { - DisplayAsPercentage = true - }, - new OsuSpriteText { Text = "Toggles:" }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" }, - showPlayfieldAreaToggle = new PlayerCheckbox { LabelText = "Show playfield area" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; } @@ -64,11 +53,9 @@ namespace osu.Game.Screens.Play.PlayerSettings { dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); - playfieldAreaDimSliderBar.Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); - showPlayfieldAreaToggle.Current = config.GetBindable(OsuSetting.ShowPlayfieldArea); } } } From 6a144fba80a4ec8b8c10367010d5c02f8129d7f6 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Mon, 20 Jul 2020 17:24:17 +0700 Subject: [PATCH 005/322] add epilepsy warning in metadata display --- .../Screens/Play/BeatmapMetadataDisplay.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index a84a85ea47..069ac69622 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -49,6 +49,37 @@ namespace osu.Game.Screens.Play } } + private class EpilepsyWarning : FillFlowContainer + { + public EpilepsyWarning() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Vertical; + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(40) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "This beatmap contains scenes with rapidly flashing colours." + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Please take caution if you are affected by epilepsy." + } + }; + } + } + private readonly WorkingBeatmap beatmap; private readonly Bindable> mods; private readonly Drawable facade; @@ -162,6 +193,12 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 20 }, Current = mods + }, + new EpilepsyWarning + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 20 }, } }, } From acbf13ddc4050e3462f34ab4c3bf5075aff1f8a5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Mon, 20 Jul 2020 17:36:42 +0700 Subject: [PATCH 006/322] add epilepsy warning field --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 ++ osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3860f12baa..da4c4ca36b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -91,6 +91,8 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } + public bool EpilepsyWarning { get; set; } + // Editor // This bookmarks stuff is necessary because DB doesn't know how to store int[] [JsonIgnore] diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b30ec0ca2c..fd17e38a4f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -175,6 +175,9 @@ namespace osu.Game.Beatmaps.Formats case @"WidescreenStoryboard": beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; + case @"EpilepsyWarning": + beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; + break; } } From 055e31ddd54b7867589261ae632bc14c5103bf08 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Mon, 20 Jul 2020 18:37:02 +0700 Subject: [PATCH 007/322] update minor --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index da4c4ca36b..b7946d53ca 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -91,6 +91,7 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } + [NotMapped] public bool EpilepsyWarning { get; set; } // Editor diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 069ac69622..f672a4db6c 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -51,8 +51,13 @@ namespace osu.Game.Screens.Play private class EpilepsyWarning : FillFlowContainer { - public EpilepsyWarning() + public EpilepsyWarning(bool warning) { + if (warning) + this.Show(); + else + this.Hide(); + AutoSizeAxes = Axes.Both; Direction = FillDirection.Vertical; Children = new Drawable[] @@ -62,7 +67,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Icon = FontAwesome.Solid.ExclamationTriangle, - Size = new Vector2(40) + Size = new Vector2(40), }, new OsuSpriteText { @@ -194,7 +199,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 }, Current = mods }, - new EpilepsyWarning + new EpilepsyWarning(beatmap.BeatmapInfo.EpilepsyWarning) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From d9fedb293a1e712fa4a4ac5902c9edf1592ba84b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Tue, 21 Jul 2020 15:48:11 +0700 Subject: [PATCH 008/322] add initial test --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 4c73065087..341924ae6d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -35,6 +35,8 @@ namespace osu.Game.Tests.Visual.Gameplay private TestPlayerLoaderContainer container; private TestPlayer player; + private bool EpilepsyWarning = false; + [Resolved] private AudioManager audioManager { get; set; } @@ -55,6 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value.BeatmapInfo.EpilepsyWarning = EpilepsyWarning; foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); @@ -240,6 +243,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for player load", () => player.IsLoaded); } + [TestCase(true)] + [TestCase(false)] + public void TestEpilepsyWarning(bool warning) + { + AddStep("change epilepsy warning", () => EpilepsyWarning = warning); + AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + } + private class TestPlayerLoaderContainer : Container { [Cached] From 95f52573f7ca180bfb366aaf5dc9f7fdddddc419 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Tue, 21 Jul 2020 15:58:25 +0700 Subject: [PATCH 009/322] change font size --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index f672a4db6c..bab141a75e 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -73,13 +73,15 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "This beatmap contains scenes with rapidly flashing colours." + Text = "This beatmap contains scenes with rapidly flashing colours.", + Font = OsuFont.GetFont(size: 20), }, new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "Please take caution if you are affected by epilepsy." + Text = "Please take caution if you are affected by epilepsy.", + Font = OsuFont.GetFont(size: 20), } }; } From fea6389f693947dd22d8f4bda9ecc30c33278cdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 12:41:06 +0900 Subject: [PATCH 010/322] Hide HUD elements during break time by default --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 8 ++-- osu.Game/Configuration/HUDVisibilityMode.cs | 17 ++++++++ osu.Game/Configuration/OsuConfigManager.cs | 4 +- .../Sections/Gameplay/GeneralSettings.cs | 6 +-- osu.Game/Screens/Play/HUDOverlay.cs | 42 +++++++++++++++---- osu.Game/Screens/Play/Player.cs | 1 + 6 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Configuration/HUDVisibilityMode.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index c192a7b0e0..e84e3cc930 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -65,17 +65,17 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExternalHideDoesntAffectConfig() { - bool originalConfigValue = false; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.DuringGameplay; createNew(); - AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface)); + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); - AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode)); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); - AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode)); } [Test] diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs new file mode 100644 index 0000000000..2b133b1bcf --- /dev/null +++ b/osu.Game/Configuration/HUDVisibilityMode.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 System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum HUDVisibilityMode + { + Never, + + [Description("Hide during breaks")] + DuringGameplay, + + Always + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 268328272c..3ce71e8549 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -85,7 +85,7 @@ namespace osu.Game.Configuration Set(OsuSetting.HitLighting, true); - Set(OsuSetting.ShowInterface, true); + Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.DuringGameplay); Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.FadePlayfieldWhenHealthLow, true); @@ -184,7 +184,7 @@ namespace osu.Game.Configuration AlwaysPlayFirstComboBreak, ScoreMeter, FloatingComments, - ShowInterface, + HUDVisibilityMode, ShowProgressGraph, ShowHealthDisplayWhenCantFail, FadePlayfieldWhenHealthLow, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 93a02ea0e4..af71c4f4e8 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -36,10 +36,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Lighten playfield during breaks", Bindable = config.GetBindable(OsuSetting.LightenDuringBreaks) }, - new SettingsCheckbox + new SettingsEnumDropdown { - LabelText = "Show score overlay", - Bindable = config.GetBindable(OsuSetting.ShowInterface) + LabelText = "Score overlay (HUD) mode", + Bindable = config.GetBindable(OsuSetting.HUDVisibilityMode) }, new SettingsCheckbox { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f09745cf71..ef1f80e0d5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play /// public Bindable ShowHud { get; } = new BindableBool(); - private Bindable configShowHud; + private Bindable configVisibilityMode; private readonly Container visibilityContainer; @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; + internal readonly IBindable IsBreakTime = new Bindable(); + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -139,9 +141,9 @@ namespace osu.Game.Screens.Play ModDisplay.Current.Value = mods; - configShowHud = config.GetBindable(OsuSetting.ShowInterface); + configVisibilityMode = config.GetBindable(OsuSetting.HUDVisibilityMode); - if (!configShowHud.Value && !hasShownNotificationOnce) + if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce) { hasShownNotificationOnce = true; @@ -177,15 +179,33 @@ namespace osu.Game.Screens.Play } }, true); - configShowHud.BindValueChanged(visible => - { - if (!ShowHud.Disabled) - ShowHud.Value = visible.NewValue; - }, true); + IsBreakTime.BindValueChanged(_ => updateVisibility()); + configVisibilityMode.BindValueChanged(_ => updateVisibility(), true); replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } + private void updateVisibility() + { + if (ShowHud.Disabled) + return; + + switch (configVisibilityMode.Value) + { + case HUDVisibilityMode.Never: + ShowHud.Value = false; + break; + + case HUDVisibilityMode.DuringGameplay: + ShowHud.Value = replayLoaded.Value || !IsBreakTime.Value; + break; + + case HUDVisibilityMode.Always: + ShowHud.Value = true; + break; + } + } + private void replayLoadedValueChanged(ValueChangedEvent e) { PlayerSettingsOverlay.ReplayLoaded = e.NewValue; @@ -202,6 +222,8 @@ namespace osu.Game.Screens.Play ModDisplay.Delay(2000).FadeOut(200); KeyCounter.Margin = new MarginPadding(10); } + + updateVisibility(); } protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) @@ -222,7 +244,9 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - configShowHud.Value = !configShowHud.Value; + configVisibilityMode.Value = configVisibilityMode.Value != HUDVisibilityMode.Never + ? HUDVisibilityMode.Never + : HUDVisibilityMode.DuringGameplay; return true; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..50b2d5a021 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -612,6 +612,7 @@ namespace osu.Game.Screens.Play // bind component bindables. Background.IsBreakTime.BindTo(breakTracker.IsBreakTime); + HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 19a0eaade9b48f65bbf51ebbac98ba32b98b9dba Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Thu, 6 Aug 2020 04:41:44 +0200 Subject: [PATCH 011/322] Allow storyboard sprites to load textures from skins --- .../Drawables/DrawableStoryboardSprite.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index d8d3248659..d40af903a6 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { @@ -17,6 +18,12 @@ namespace osu.Game.Storyboards.Drawables { public StoryboardSprite Sprite { get; } + private ISkinSource currentSkin; + + private TextureStore storyboardTextureStore; + + private string texturePath; + private bool flipH; public bool FlipH @@ -114,14 +121,36 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(ISkinSource skin, IBindable beatmap, TextureStore textureStore) { - var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - if (path == null) - return; + if (skin != null) + { + currentSkin = skin; + skin.SourceChanged += onChange; + } + + storyboardTextureStore = textureStore; + + texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + skinChanged(); - Texture = textureStore.Get(path); Sprite.ApplyTransforms(this); } + + private void onChange() => + // schedule required to avoid calls after disposed. + // note that this has the side-effect of components only performing a possible texture change when they are alive. + Scheduler.AddOnce(skinChanged); + + private void skinChanged() + { + var newTexture = currentSkin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); + + if (Texture == newTexture) return; + + Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) + Texture = newTexture; + } } } From e0ae2b3ebf20e1454af64b001e81365e301f221b Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Thu, 6 Aug 2020 17:07:36 +0200 Subject: [PATCH 012/322] Switch to SkinReloadableDrawable --- .../Drawables/DrawableStoryboardSprite.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index d40af903a6..d4f27bf4aa 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -14,11 +14,11 @@ using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardSprite : Sprite, IFlippable, IVectorScalable + public class DrawableStoryboardSprite : SkinReloadableDrawable, IFlippable, IVectorScalable { public StoryboardSprite Sprite { get; } - private ISkinSource currentSkin; + private Sprite drawableSprite; private TextureStore storyboardTextureStore; @@ -123,34 +123,28 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(ISkinSource skin, IBindable beatmap, TextureStore textureStore) { - if (skin != null) + InternalChild = drawableSprite = new Sprite { - currentSkin = skin; - skin.SourceChanged += onChange; - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; storyboardTextureStore = textureStore; texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - skinChanged(); - Sprite.ApplyTransforms(this); } - private void onChange() => - // schedule required to avoid calls after disposed. - // note that this has the side-effect of components only performing a possible texture change when they are alive. - Scheduler.AddOnce(skinChanged); - - private void skinChanged() + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - var newTexture = currentSkin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); + base.SkinChanged(skin, allowFallback); + var newTexture = skin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); - if (Texture == newTexture) return; + if (drawableSprite.Texture == newTexture) return; - Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) - Texture = newTexture; + drawableSprite.Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) + drawableSprite.Texture = newTexture; } } } From 7cf225520fdfb93bffc18fc84eed087aa29d7cea Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Sat, 8 Aug 2020 02:43:39 +0200 Subject: [PATCH 013/322] Change from BDL to Resolved --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index d4f27bf4aa..45c74da892 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -20,7 +20,8 @@ namespace osu.Game.Storyboards.Drawables private Sprite drawableSprite; - private TextureStore storyboardTextureStore; + [Resolved] + private TextureStore storyboardTextureStore { get; set; } private string texturePath; @@ -121,7 +122,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap) { InternalChild = drawableSprite = new Sprite { @@ -129,8 +130,6 @@ namespace osu.Game.Storyboards.Drawables Origin = Anchor.Centre }; - storyboardTextureStore = textureStore; - texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; Sprite.ApplyTransforms(this); From d9ba677773fc3945c3e62d6f16a9bdf5ec2f9fa5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 24 Aug 2020 15:08:50 +0200 Subject: [PATCH 014/322] Change TeamFlag from sprite to a container with a sprite --- .../Components/DrawableTeamFlag.cs | 20 +++++++++++++++++-- .../Components/DrawableTournamentTeam.cs | 11 ++-------- .../Ladder/Components/DrawableMatchTeam.cs | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index 8c85c9a46f..a2e0bf83be 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -4,19 +4,24 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Tournament.Models; +using osuTK; namespace osu.Game.Tournament.Components { - public class DrawableTeamFlag : Sprite + public class DrawableTeamFlag : Container { private readonly TournamentTeam team; [UsedImplicitly] private Bindable flag; + private Sprite flagSprite; + public DrawableTeamFlag(TournamentTeam team) { this.team = team; @@ -27,7 +32,18 @@ namespace osu.Game.Tournament.Components { if (team == null) return; - (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => Texture = textures.Get($@"Flags/{team.FlagName}"), true); + Size = new Vector2(70, 47); + Masking = true; + CornerRadius = 5; + Child = flagSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill + }; + + (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true); } } } diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index f8aed26ce1..b9442a67f5 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -4,9 +4,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Tournament.Models; @@ -17,7 +15,7 @@ namespace osu.Game.Tournament.Components { public readonly TournamentTeam Team; - protected readonly Sprite Flag; + protected readonly Container Flag; protected readonly TournamentSpriteText AcronymText; [UsedImplicitly] @@ -27,12 +25,7 @@ namespace osu.Game.Tournament.Components { Team = team; - Flag = new DrawableTeamFlag(team) - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - }; - + Flag = new DrawableTeamFlag(team); AcronymText = new TournamentSpriteText { Font = OsuFont.Torus.With(weight: FontWeight.Regular), diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 15cb7e44cb..030ccb5cb3 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components this.losers = losers; Size = new Vector2(150, 40); - Flag.Scale = new Vector2(0.9f); + Flag.Scale = new Vector2(0.6f); Flag.Anchor = Flag.Origin = Anchor.CentreLeft; AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; From 18ae17e1290a936a4f854d6cbf740c2ad9dbe1f0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 13 Sep 2020 19:55:21 +0200 Subject: [PATCH 015/322] Add scale to GroupTeam and remove unnecessary sizing and scaling in other scenes --- osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs | 1 + osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs | 1 - osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs index 4f0ce0bbe7..119f71ebfa 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components AcronymText.Origin = Anchor.TopCentre; AcronymText.Text = team.Acronym.Value.ToUpperInvariant(); AcronymText.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 10); + Flag.Scale = new Vector2(0.5f); InternalChildren = new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index b01c93ae03..48aea46497 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -29,7 +29,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components var anchor = flip ? Anchor.TopLeft : Anchor.TopRight; Flag.RelativeSizeAxes = Axes.None; - Flag.Size = new Vector2(60, 40); Flag.Origin = anchor; Flag.Anchor = anchor; diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 3870f486e1..dde140ab91 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -90,8 +90,6 @@ namespace osu.Game.Tournament.Screens.TeamWin { new DrawableTeamFlag(match.Winner) { - Size = new Vector2(300, 200), - Scale = new Vector2(0.5f), Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(-300, 10), From a8cbd400d36b8997a0ba87ae1ec55227381764fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:17:13 +0900 Subject: [PATCH 016/322] Ensure virtual track time is long enough for test beatmaps --- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 5 ++--- osu.Game/Tests/Visual/OsuTestScene.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index ab4fb38657..1e43e5d148 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -180,9 +180,8 @@ namespace osu.Game.Tests.Beatmaps private readonly BeatmapInfo skinBeatmapInfo; private readonly IResourceStore resourceStore; - public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, - double length = 60000) - : base(beatmap, storyboard, referenceClock, audio, length) + public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) + : base(beatmap, storyboard, referenceClock, audio) { this.skinBeatmapInfo = skinBeatmapInfo; this.resourceStore = resourceStore; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b59a1db403..6e2fd0a6d7 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -23,6 +23,7 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens; using osu.Game.Storyboards; @@ -222,18 +223,19 @@ namespace osu.Game.Tests.Visual /// The storyboard. /// An optional clock which should be used instead of a stopwatch for virtual time progression. /// Audio manager. Required if a reference clock isn't provided. - /// The length of the returned virtual track. - public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) + public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { + double lastObjectTime = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 60000; + if (referenceClock != null) { store = new TrackVirtualStore(referenceClock); audio.AddItem(store); - track = store.GetVirtual(length); + track = store.GetVirtual(lastObjectTime); } else - track = audio?.Tracks.GetVirtual(length); + track = audio?.Tracks.GetVirtual(lastObjectTime); } ~ClockBackedTestWorkingBeatmap() From 21bf93a7c2b6d07e4e825c6b14f59a4ea3edd0af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:29:36 +0900 Subject: [PATCH 017/322] Ensure there's a buffer after the last hitobject to allow certain replay tests to complete correctly --- osu.Game/Tests/Visual/OsuTestScene.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 6e2fd0a6d7..e3f07dbad4 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -226,16 +226,18 @@ namespace osu.Game.Tests.Visual public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { - double lastObjectTime = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 60000; + var lastHitObject = beatmap.HitObjects.LastOrDefault(); + + double trackLength = lastHitObject?.GetEndTime() + 2000 ?? 60000; if (referenceClock != null) { store = new TrackVirtualStore(referenceClock); audio.AddItem(store); - track = store.GetVirtual(lastObjectTime); + track = store.GetVirtual(trackLength); } else - track = audio?.Tracks.GetVirtual(lastObjectTime); + track = audio?.Tracks.GetVirtual(trackLength); } ~ClockBackedTestWorkingBeatmap() From 4b8188065504cf88de4c8cb487a180f1a0696904 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 14:04:04 +0900 Subject: [PATCH 018/322] Account for potentially longer non-last objects --- osu.Game/Tests/Visual/OsuTestScene.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index e3f07dbad4..8886188d95 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -226,9 +226,11 @@ namespace osu.Game.Tests.Visual public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { - var lastHitObject = beatmap.HitObjects.LastOrDefault(); + double trackLength = 60000; - double trackLength = lastHitObject?.GetEndTime() + 2000 ?? 60000; + if (beatmap.HitObjects.Count > 0) + // add buffer after last hitobject to allow for final replay frames etc. + trackLength = beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000; if (referenceClock != null) { From 7fead6ee41dcf4a242eec2d0b13df47d6c2fd50c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 14:22:32 +0900 Subject: [PATCH 019/322] Add comment making mania test behaviour clearer --- osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index ab840e1c46..e8c2472c3b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests objects.Add(new Note { StartTime = time }); + // don't hit the first note if (i > 0) { frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1)); From cf76d777623d32947d1285384c71f540a586dea3 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Fri, 9 Oct 2020 17:34:01 +0200 Subject: [PATCH 020/322] Fix osu!classic skin elements not showing up in storyboards --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 45c74da892..cd09cafbce 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -138,7 +139,8 @@ namespace osu.Game.Storyboards.Drawables protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); - var newTexture = skin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); + + var newTexture = skin?.GetTexture(Path.GetFileNameWithoutExtension(Sprite.Path)) ?? storyboardTextureStore?.Get(texturePath); if (drawableSprite.Texture == newTexture) return; From f41fc71e42c9301889a8cff230a723a8ba8007d8 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Fri, 9 Oct 2020 18:02:21 +0200 Subject: [PATCH 021/322] Allow storyboard animations to load textures from skins --- .../Drawables/DrawableStoryboardAnimation.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 72e52f6106..963cf37fea 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,13 +12,22 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardAnimation : TextureAnimation, IFlippable, IVectorScalable + public class DrawableStoryboardAnimation : SkinReloadableDrawable, IFlippable, IVectorScalable { public StoryboardAnimation Animation { get; } + private TextureAnimation drawableTextureAnimation; + + [Resolved] + private TextureStore storyboardTextureStore { get; set; } + + private readonly List texturePathsRaw = new List(); + private readonly List texturePaths = new List(); + private bool flipH; public bool FlipH @@ -108,28 +119,48 @@ namespace osu.Game.Storyboards.Drawables Animation = animation; Origin = animation.Origin; Position = animation.InitialPosition; - Loop = animation.LoopType == AnimationLoopType.LoopForever; LifetimeStart = animation.StartTime; LifetimeEnd = animation.EndTime; } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap) { + InternalChild = drawableTextureAnimation = new TextureAnimation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Loop = Animation.LoopType == AnimationLoopType.LoopForever + }; + for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = Animation.Path.Replace(".", frame + "."); + texturePathsRaw.Add(Path.GetFileNameWithoutExtension(framePath)); var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - if (path == null) - continue; - - var texture = textureStore.Get(path); - AddFrame(texture, Animation.FrameDelay); + texturePaths.Add(path); } Animation.ApplyTransforms(this); } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + drawableTextureAnimation.ClearFrames(); + + for (var frame = 0; frame < Animation.FrameCount; frame++) + { + var texture = skin?.GetTexture(texturePathsRaw[frame]) ?? storyboardTextureStore?.Get(texturePaths[frame]); + + if (texture == null) + continue; + + drawableTextureAnimation.AddFrame(texture, Animation.FrameDelay); + } + } } } From 41d82e3e8ab5377955e7b978e749de7f52fc82ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:34:11 +0900 Subject: [PATCH 022/322] 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 3df894fbcc..c3c755ecd7 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 8b10f0a7f7..e8498129a1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 88abbca73d..4b38d9f22d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From b08e7ce1f52743307e4c78c3feafc298b0c4dfc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:37:24 +0900 Subject: [PATCH 023/322] Update BaseColour specification --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index be3bca3242..9aff4ddf8f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform = new WaveformGraph { RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), + BaseColour = colours.Blue.Opacity(0.2f), LowColour = colours.BlueLighter, MidColour = colours.BlueDark, HighColour = colours.BlueDarker, From a393bbe8f7c905997d5e0c5717609d31848e4b99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 12:37:32 +0900 Subject: [PATCH 024/322] Remove direct drawable storage from carousel models --- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- .../Screens/Select/Carousel/CarouselGroup.cs | 18 +-------------- .../Screens/Select/Carousel/CarouselItem.cs | 23 ++----------------- 4 files changed, 5 insertions(+), 40 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 83e3c84f39..6a18e2d6a3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Select.Carousel State.Value = CarouselItemState.Collapsed; } - protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this); + public override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this); public override void Filter(FilterCriteria criteria) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 92ccfde14b..75d40a6b03 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel .ForEach(AddChild); } - protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); + public override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); protected override CarouselItem GetNextToSelect() { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index aa48d1a04e..b85e868b89 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select.Carousel /// public class CarouselGroup : CarouselItem { - protected override DrawableCarouselItem CreateDrawableRepresentation() => null; + public override DrawableCarouselItem CreateDrawableRepresentation() => null; public IReadOnlyList Children => InternalChildren; @@ -23,22 +23,6 @@ namespace osu.Game.Screens.Select.Carousel /// private ulong currentChildID; - public override List Drawables - { - get - { - var drawables = base.Drawables; - - // if we are explicitly not present, don't ever present children. - // without this check, children drawables can potentially be presented without their group header. - if (DrawableRepresentation.Value?.IsPresent == false) return drawables; - - foreach (var c in InternalChildren) - drawables.AddRange(c.Drawables); - return drawables; - } - } - public virtual void RemoveChild(CarouselItem i) { InternalChildren.Remove(i); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 79c1a4cb6b..555c32c041 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -1,8 +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 osu.Framework.Bindables; namespace osu.Game.Screens.Select.Carousel @@ -18,23 +16,8 @@ namespace osu.Game.Screens.Select.Carousel /// public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered.Value; - public virtual List Drawables - { - get - { - var items = new List(); - - var self = DrawableRepresentation.Value; - if (self?.IsPresent == true) items.Add(self); - - return items; - } - } - protected CarouselItem() { - DrawableRepresentation = new Lazy(CreateDrawableRepresentation); - Filtered.ValueChanged += filtered => { if (filtered.NewValue && State.Value == CarouselItemState.Selected) @@ -42,17 +25,15 @@ namespace osu.Game.Screens.Select.Carousel }; } - protected readonly Lazy DrawableRepresentation; - /// /// Used as a default sort method for s of differing types. /// internal ulong ChildID; /// - /// Create a fresh drawable version of this item. If you wish to consume the current representation, use instead. + /// Create a fresh drawable version of this item. /// - protected abstract DrawableCarouselItem CreateDrawableRepresentation(); + public abstract DrawableCarouselItem CreateDrawableRepresentation(); public virtual void Filter(FilterCriteria criteria) { From 9193f5b0ba5b7208818898612e4fb2cefaeef379 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 12:37:41 +0900 Subject: [PATCH 025/322] Expose panel height from non-drawable models --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 ++ osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 ++ osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 75d40a6b03..44b8e72d51 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { + public float TotalHeight => DrawableCarouselBeatmapSet.HEIGHT + BeatmapSet.Beatmaps.Count * DrawableCarouselBeatmap.HEIGHT; + public IEnumerable Beatmaps => InternalChildren.OfType(); public BeatmapSetInfo BeatmapSet; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 10745fe3c1..c8f9507b91 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { + public const float HEIGHT = MAX_HEIGHT; + private readonly BeatmapInfo beatmap; private Sprite background; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 3c8ac69dd2..8332093a3c 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasContextMenu { + public const float HEIGHT = MAX_HEIGHT; + private Action restoreHiddenRequested; private Action viewDetails; From 3143224e5b5b51f939c6130dd026de24bb3e96db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 14:23:18 +0900 Subject: [PATCH 026/322] Refactor how drawable carousel items are constructed --- .../SongSelect/TestSceneBeatmapCarousel.cs | 29 +-- osu.Game/Screens/Select/BeatmapCarousel.cs | 187 ++++++++---------- .../Select/Carousel/CarouselBeatmap.cs | 2 + .../Select/Carousel/CarouselBeatmapSet.cs | 15 +- .../Screens/Select/Carousel/CarouselItem.cs | 2 + .../Carousel/DrawableCarouselBeatmap.cs | 6 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 43 ++++ .../Select/Carousel/DrawableCarouselItem.cs | 35 ++-- 8 files changed, 177 insertions(+), 142 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 3aff390a47..680928b331 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -709,19 +709,20 @@ namespace osu.Game.Tests.Visual.SongSelect private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null) { - createCarousel(carouselAdjust); - - if (beatmapSets == null) - { - beatmapSets = new List(); - - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); - } - bool changed = false; - AddStep($"Load {(beatmapSets.Count > 0 ? beatmapSets.Count.ToString() : "some")} beatmaps", () => + + createCarousel(c => { + carouselAdjust?.Invoke(c); + + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + } + carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; @@ -807,7 +808,7 @@ namespace osu.Game.Tests.Visual.SongSelect private bool selectedBeatmapVisible() { - var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); + var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); if (currentlySelected == null) return true; @@ -908,10 +909,10 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestBeatmapCarousel : BeatmapCarousel { - public new List Items => base.Items; - public bool PendingFilterTask => PendingFilter != null; + public IEnumerable Items => InternalChildren.OfType(); + protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty(); } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5f6f859d66..ef533a1456 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -96,9 +96,6 @@ namespace osu.Game.Screens.Select beatmapSets.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); - // preload drawables as the ctor overhead is quite high currently. - _ = newRoot.Drawables; - root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; @@ -119,6 +116,8 @@ namespace osu.Game.Screens.Select } private readonly List yPositions = new List(); + private readonly List visibleItems = new List(); + private readonly Cached itemsCache = new Cached(); private readonly Cached scrollPositionCache = new Cached(); @@ -130,8 +129,6 @@ namespace osu.Game.Screens.Select private readonly List previouslyVisitedRandomSets = new List(); private readonly Stack randomSelectedBeatmaps = new Stack(); - protected List Items = new List(); - private CarouselRoot root; private IBindable> itemUpdated; @@ -178,7 +175,8 @@ namespace osu.Game.Screens.Select itemRestored = beatmaps.BeatmapRestored.GetBoundCopy(); itemRestored.BindValueChanged(beatmapRestored); - loadBeatmapSets(GetLoadableBeatmaps()); + if (!beatmapSets.Any()) + loadBeatmapSets(GetLoadableBeatmaps()); } protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.AllButFiles); @@ -558,71 +556,78 @@ namespace osu.Game.Screens.Select { base.Update(); + //todo: this should only refresh items, not everything here if (!itemsCache.IsValid) + { updateItems(); - // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); - // Find index range of all items that should be on-screen - Trace.Assert(Items.Count == yPositions.Count); + // Find index range of all items that should be on-screen + int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); + if (firstIndex < 0) firstIndex = ~firstIndex; + int lastIndex = yPositions.BinarySearch(visibleBottomBound); + if (lastIndex < 0) lastIndex = ~lastIndex; - int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); - if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(visibleBottomBound); - if (lastIndex < 0) lastIndex = ~lastIndex; + scrollableContent.Clear(); - int notVisibleCount = 0; - - // Add those items within the previously found index range that should be displayed. - for (int i = firstIndex; i < lastIndex; ++i) - { - DrawableCarouselItem item = Items[i]; - - if (!item.Item.Visible) + // Add those items within the previously found index range that should be displayed. + for (int i = firstIndex; i < lastIndex; ++i) { - if (!item.IsPresent) - notVisibleCount++; - continue; - } + DrawableCarouselItem item = visibleItems[i].CreateDrawableRepresentation(); - float depth = i + (item is DrawableCarouselBeatmapSet ? -Items.Count : 0); + item.Y = yPositions[i]; + item.Depth = i; - // Only add if we're not already part of the content. - if (!scrollableContent.Contains(item)) - { - // Makes sure headers are always _below_ items, - // and depth flows downward. - item.Depth = depth; + scrollableContent.Add(item); - switch (item.LoadState) + // if (!item.Item.Visible) + // { + // if (!item.IsPresent) + // notVisibleCount++; + // continue; + // } + + // Only add if we're not already part of the content. + /* + if (!scrollableContent.Contains(item)) { - case LoadState.NotLoaded: - LoadComponentAsync(item); - break; + // Makes sure headers are always _below_ items, + // and depth flows downward. + item.Depth = depth; - case LoadState.Loading: - break; + switch (item.LoadState) + { + case LoadState.NotLoaded: + LoadComponentAsync(item); + break; - default: - scrollableContent.Add(item); - break; + case LoadState.Loading: + break; + + default: + scrollableContent.Add(item); + break; + } } - } - else - { - scrollableContent.ChangeChildDepth(item, depth); + else + { + scrollableContent.ChangeChildDepth(item, depth); + } + */ } } - // this is not actually useful right now, but once we have groups may well be. - if (notVisibleCount > 50) - itemsCache.Invalidate(); - // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). foreach (DrawableCarouselItem p in scrollableContent.Children) + { updateItem(p); + + // foreach (var pChild in p.ChildItems) + // updateItem(pChild, p); + } } protected override void UpdateAfterChildren() @@ -633,15 +638,6 @@ namespace osu.Game.Screens.Select updateScrollPosition(); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - // aggressively dispose "off-screen" items to reduce GC pressure. - foreach (var i in Items) - i.Dispose(); - } - private void beatmapRemoved(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) @@ -704,69 +700,39 @@ namespace osu.Game.Screens.Select /// The Y position of the currently selected item. private void updateItems() { - Items = root.Drawables.ToList(); - yPositions.Clear(); + visibleItems.Clear(); float currentY = visibleHalfHeight; - DrawableCarouselBeatmapSet lastSet = null; scrollTarget = null; - foreach (DrawableCarouselItem d in Items) + foreach (CarouselItem item in root.Children) { - if (d.IsPresent) + if (item.Filtered.Value) + continue; + + switch (item) { - switch (d) + case CarouselBeatmapSet set: { - case DrawableCarouselBeatmapSet set: - { - lastSet = set; + visibleItems.Add(set); + yPositions.Add(currentY); + //lastSet = set; - set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - set.MoveToY(currentY, 750, Easing.OutExpo); - break; - } - - case DrawableCarouselBeatmap beatmap: - { - if (beatmap.Item.State.Value == CarouselItemState.Selected) - // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space - // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas) - // then reapply the top semi-transparent area (because carousel's screen space starts below it) - // and finally add half of the panel's own height to achieve vertical centering of the panel itself - scrollTarget = currentY - visibleHalfHeight + BleedTop + beatmap.DrawHeight / 2; - - void performMove(float y, float? startY = null) - { - if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value)); - beatmap.MoveToX(beatmap.Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); - beatmap.MoveToY(y, 750, Easing.OutExpo); - } - - Debug.Assert(lastSet != null); - - float? setY = null; - if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override. - setY = lastSet.Y + lastSet.DrawHeight + 5; - - if (d.IsLoaded) - performMove(currentY, setY); - else - { - float y = currentY; - d.OnLoadComplete += _ => performMove(y, setY); - } - - break; - } + // TODO: move this logic to DCBS too. + // set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + // set.MoveToY(currentY, 750, Easing.OutExpo); + currentY += set.TotalHeight; + break; } + + default: + continue; + // + // break; + // } } - - yPositions.Add(currentY); - - if (d.Item.Visible) - currentY += d.DrawHeight + 5; } currentY += visibleHalfHeight; @@ -869,6 +835,7 @@ namespace osu.Game.Screens.Select /// public bool UserScrolling { get; private set; } + // ReSharper disable once OptionalParameterHierarchyMismatch fuck off rider protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { UserScrolling = true; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 6a18e2d6a3..dce4028f17 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -10,6 +10,8 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmap : CarouselItem { + public override float TotalHeight => DrawableCarouselBeatmap.HEIGHT; + public readonly BeatmapInfo Beatmap; public CarouselBeatmap(BeatmapInfo beatmap) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 44b8e72d51..e34710f71d 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -12,7 +12,20 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { - public float TotalHeight => DrawableCarouselBeatmapSet.HEIGHT + BeatmapSet.Beatmaps.Count * DrawableCarouselBeatmap.HEIGHT; + public override float TotalHeight + { + get + { + switch (State.Value) + { + case CarouselItemState.Selected: + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count * DrawableCarouselBeatmap.HEIGHT; + + default: + return DrawableCarouselBeatmapSet.HEIGHT; + } + } + } public IEnumerable Beatmaps => InternalChildren.OfType(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 555c32c041..004d9779ef 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -7,6 +7,8 @@ namespace osu.Game.Screens.Select.Carousel { public abstract class CarouselItem { + public virtual float TotalHeight => 0; + public readonly BindableBool Filtered = new BindableBool(); public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index c8f9507b91..7d69251265 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { - public const float HEIGHT = MAX_HEIGHT; + public const float HEIGHT = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmap; @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select.Carousel : base(panel) { beatmap = panel.Beatmap; - Height *= 0.60f; + Height = HEIGHT; } [BackgroundDependencyLoader(true)] @@ -170,6 +170,8 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); + BorderContainer.MoveToX(Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), new Color4(40, 86, 102, 255)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8332093a3c..aaa925c6f8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -43,8 +43,13 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + public override IEnumerable ChildItems => beatmapContainer?.Children ?? base.ChildItems; + private readonly BeatmapSetInfo beatmapSet; + private Container beatmapContainer; + private Bindable beatmapSetState; + public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) : base(set) { @@ -119,6 +124,44 @@ namespace osu.Game.Screens.Select.Carousel } } }; + + // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. + AddRangeInternal(new Drawable[] + { + beatmapContainer = new Container + { + X = 50, + Y = MAX_HEIGHT, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }); + + beatmapSetState = Item.State.GetBoundCopy(); + beatmapSetState.BindValueChanged(setSelected, true); + } + + private void setSelected(ValueChangedEvent obj) + { + switch (obj.NewValue) + { + default: + beatmapContainer.Clear(); + break; + + case CarouselItemState.Selected: + + float yPos = 0; + + foreach (var item in ((CarouselBeatmapSet)Item).Beatmaps.Select(b => b.CreateDrawableRepresentation()).OfType()) + { + item.Y = yPos; + beatmapContainer.Add(item); + yPos += item.Item.TotalHeight; + } + + break; + } } private const int maximum_difficulty_icons = 18; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 121491d6ca..c0405b373d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -27,10 +29,13 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselItem Item; - private Container nestedContainer; - private Container borderContainer; + public virtual IEnumerable ChildItems => Enumerable.Empty(); - private Box hoverLayer; + private readonly Container nestedContainer; + + protected readonly Container BorderContainer; + + private readonly Box hoverLayer; protected override Container Content => nestedContainer; @@ -41,14 +46,8 @@ namespace osu.Game.Screens.Select.Carousel Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; Alpha = 0; - } - private SampleChannel sampleHover; - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { - InternalChild = borderContainer = new Container + InternalChild = BorderContainer = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -68,7 +67,13 @@ namespace osu.Game.Screens.Select.Carousel }, } }; + } + private SampleChannel sampleHover; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); hoverLayer.Colour = colours.Blue.Opacity(0.1f); } @@ -87,7 +92,7 @@ namespace osu.Game.Screens.Select.Carousel base.OnHoverLost(e); } - public void SetMultiplicativeAlpha(float alpha) => borderContainer.Alpha = alpha; + public void SetMultiplicativeAlpha(float alpha) => BorderContainer.Alpha = alpha; protected override void LoadComplete() { @@ -123,8 +128,8 @@ namespace osu.Game.Screens.Select.Carousel { Item.State.Value = CarouselItemState.Selected; - borderContainer.BorderThickness = 2.5f; - borderContainer.EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = 2.5f; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), @@ -137,8 +142,8 @@ namespace osu.Game.Screens.Select.Carousel { Item.State.Value = CarouselItemState.NotSelected; - borderContainer.BorderThickness = 0; - borderContainer.EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = 0; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(1), From f17d661c1a1e1aa67865cb09e5e9e492cfbaa448 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 14:46:51 +0900 Subject: [PATCH 027/322] Add basic range-based invalidation --- osu.Game/Screens/Select/BeatmapCarousel.cs | 70 +++++++--------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ef533a1456..94ba0caefb 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -552,70 +552,44 @@ namespace osu.Game.Screens.Select #endregion + private (int first, int last) displayedRange; + protected override void Update() { base.Update(); + bool revalidateItems = !itemsCache.IsValid; + //todo: this should only refresh items, not everything here - if (!itemsCache.IsValid) - { + if (revalidateItems) updateItems(); - // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); - // Find index range of all items that should be on-screen - int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); - if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(visibleBottomBound); - if (lastIndex < 0) lastIndex = ~lastIndex; + // Find index range of all items that should be on-screen + int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); + if (firstIndex < 0) firstIndex = ~firstIndex; + int lastIndex = yPositions.BinarySearch(visibleBottomBound); + if (lastIndex < 0) lastIndex = ~lastIndex; - scrollableContent.Clear(); + if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) + { + displayedRange = (firstIndex, lastIndex); // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { - DrawableCarouselItem item = visibleItems[i].CreateDrawableRepresentation(); + var panel = scrollableContent.FirstOrDefault(c => c.Item == visibleItems[i]); - item.Y = yPositions[i]; - item.Depth = i; - - scrollableContent.Add(item); - - // if (!item.Item.Visible) - // { - // if (!item.IsPresent) - // notVisibleCount++; - // continue; - // } - - // Only add if we're not already part of the content. - /* - if (!scrollableContent.Contains(item)) + if (panel == null) { - // Makes sure headers are always _below_ items, - // and depth flows downward. - item.Depth = depth; - - switch (item.LoadState) - { - case LoadState.NotLoaded: - LoadComponentAsync(item); - break; - - case LoadState.Loading: - break; - - default: - scrollableContent.Add(item); - break; - } + panel = visibleItems[i].CreateDrawableRepresentation(); + scrollableContent.Add(panel); } - else - { - scrollableContent.ChangeChildDepth(item, depth); - } - */ + + panel.Y = yPositions[i]; + scrollableContent.ChangeChildDepth(panel, i); } } From 0a978c6131352794a60ea3c00db373c59d0144af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:36:03 +0900 Subject: [PATCH 028/322] Add basic pooling setup --- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 ++++-- .../Select/Carousel/CarouselBeatmapSet.cs | 2 - .../Carousel/DrawableCarouselBeatmap.cs | 6 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 55 ++++++++++-------- .../Select/Carousel/DrawableCarouselItem.cs | 58 +++++++++++++++---- 5 files changed, 95 insertions(+), 43 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94ba0caefb..f5b524e57c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -16,6 +16,7 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -136,6 +137,8 @@ namespace osu.Game.Screens.Select private IBindable> itemHidden; private IBindable> itemRestored; + private readonly DrawablePool setPool = new DrawablePool(100); + public BeatmapCarousel() { root = new CarouselRoot(this); @@ -146,9 +149,13 @@ namespace osu.Game.Screens.Select { Masking = false, RelativeSizeAxes = Axes.Both, - Child = scrollableContent = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, + setPool, + scrollableContent = new Container + { + RelativeSizeAxes = Axes.X, + } } } }; @@ -580,11 +587,13 @@ namespace osu.Game.Screens.Select // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { - var panel = scrollableContent.FirstOrDefault(c => c.Item == visibleItems[i]); + var item = visibleItems[i]; + + var panel = scrollableContent.FirstOrDefault(c => c.Item == item); if (panel == null) { - panel = visibleItems[i].CreateDrawableRepresentation(); + panel = setPool.Get(p => p.Item = item); scrollableContent.Add(panel); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index e34710f71d..15f622f3c4 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -43,8 +43,6 @@ namespace osu.Game.Screens.Select.Carousel .ForEach(AddChild); } - public override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); - protected override CarouselItem GetNextToSelect() { if (LastSelected == null) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 7d69251265..4135960e06 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -60,10 +60,12 @@ namespace osu.Game.Screens.Select.Carousel private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) - : base(panel) { beatmap = panel.Beatmap; Height = HEIGHT; + + // todo: temporary + Item = panel; } [BackgroundDependencyLoader(true)] @@ -79,7 +81,7 @@ namespace osu.Game.Screens.Select.Carousel if (manager != null) hideRequested = manager.Hide; - Children = new Drawable[] + Content.Children = new Drawable[] { background = new Box { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index aaa925c6f8..0bfec4f509 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -45,26 +45,40 @@ namespace osu.Game.Screens.Select.Carousel public override IEnumerable ChildItems => beatmapContainer?.Children ?? base.ChildItems; - private readonly BeatmapSetInfo beatmapSet; + private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; private Container beatmapContainer; private Bindable beatmapSetState; - public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) - : base(set) - { - beatmapSet = set.BeatmapSet; - } + [Resolved] + private BeatmapManager manager { get; set; } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay) + private void load(BeatmapSetOverlay beatmapOverlay) { restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; - Children = new Drawable[] + // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. + AddRangeInternal(new Drawable[] + { + beatmapContainer = new Container + { + X = 50, + Y = MAX_HEIGHT, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }); + } + + protected override void UpdateItem() + { + base.UpdateItem(); + + Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { @@ -125,17 +139,7 @@ namespace osu.Game.Screens.Select.Carousel } }; - // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. - AddRangeInternal(new Drawable[] - { - beatmapContainer = new Container - { - X = 50, - Y = MAX_HEIGHT, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - }); + beatmapContainer.Clear(); beatmapSetState = Item.State.GetBoundCopy(); beatmapSetState.BindValueChanged(setSelected, true); @@ -153,11 +157,16 @@ namespace osu.Game.Screens.Select.Carousel float yPos = 0; - foreach (var item in ((CarouselBeatmapSet)Item).Beatmaps.Select(b => b.CreateDrawableRepresentation()).OfType()) + var carouselBeatmapSet = (CarouselBeatmapSet)Item; + + foreach (var item in carouselBeatmapSet.Children) { - item.Y = yPos; - beatmapContainer.Add(item); - yPos += item.Item.TotalHeight; + var beatmapPanel = item.CreateDrawableRepresentation(); + + beatmapPanel.Y = yPos; + yPos += item.TotalHeight; + + beatmapContainer.Add((DrawableCarouselBeatmap)beatmapPanel); } break; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index c0405b373d..4c4932c22a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -2,14 +2,17 @@ // 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.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -19,15 +22,32 @@ using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public abstract class DrawableCarouselItem : Container + public abstract class DrawableCarouselItem : PoolableDrawable { public const float MAX_HEIGHT = 80; - public override bool RemoveWhenNotAlive => false; + public override bool IsPresent => base.IsPresent || Item?.Visible == true; - public override bool IsPresent => base.IsPresent || Item.Visible; + public CarouselItem Item + { + get => item; + set + { + if (item == value) + return; - public readonly CarouselItem Item; + if (item != null) + { + Item.Filtered.ValueChanged -= onStateChange; + Item.State.ValueChanged -= onStateChange; + } + + item = value; + + if (IsLoaded) + UpdateItem(); + } + } public virtual IEnumerable ChildItems => Enumerable.Empty(); @@ -37,12 +57,10 @@ namespace osu.Game.Screens.Select.Carousel private readonly Box hoverLayer; - protected override Container Content => nestedContainer; + protected Container Content => nestedContainer; - protected DrawableCarouselItem(CarouselItem item) + protected DrawableCarouselItem() { - Item = item; - Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; Alpha = 0; @@ -70,6 +88,7 @@ namespace osu.Game.Screens.Select.Carousel } private SampleChannel sampleHover; + private CarouselItem item; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) @@ -98,14 +117,27 @@ namespace osu.Game.Screens.Select.Carousel { base.LoadComplete(); - ApplyState(); - Item.Filtered.ValueChanged += _ => Schedule(ApplyState); - Item.State.ValueChanged += _ => Schedule(ApplyState); + UpdateItem(); } + protected virtual void UpdateItem() + { + if (item == null) + return; + + ApplyState(); + + Item.Filtered.ValueChanged += onStateChange; + Item.State.ValueChanged += onStateChange; + } + + private void onStateChange(ValueChangedEvent obj) => Schedule(ApplyState); + + private void onStateChange(ValueChangedEvent _) => Schedule(ApplyState); + protected virtual void ApplyState() { - if (!IsLoaded) return; + Debug.Assert(Item != null); switch (Item.State.Value) { @@ -126,6 +158,8 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void Selected() { + Debug.Assert(Item != null); + Item.State.Value = CarouselItemState.Selected; BorderContainer.BorderThickness = 2.5f; From f3b24b9bb5bd9a5fa9cf28e5c190a565a8ce9df3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:36:26 +0900 Subject: [PATCH 029/322] Avoid performing eager selection constantly on adding ranges of new children --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/CarouselGroupEagerSelect.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f5b524e57c..a8212f7e59 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select { CarouselRoot newRoot = new CarouselRoot(this); - beatmapSets.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); + newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null)); root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 262bea9c71..9e8aad4b6f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; namespace osu.Game.Screens.Select.Carousel @@ -54,6 +55,14 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } + public void AddChildren(IEnumerable items) + { + foreach (var i in items) + base.AddChild(i); + + attemptSelection(); + } + public override void AddChild(CarouselItem i) { base.AddChild(i); From 580ea62710810d80a92059fced01560b7085ee26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:36:36 +0900 Subject: [PATCH 030/322] Temporarily increase test beatmap count for perf testing --- .../SongSelect/TestSceneBeatmapCarousel.cs | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 680928b331..7d850a0d13 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -835,42 +836,29 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"test set #{id}!", AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) }, - Beatmaps = new List(new[] - { - new BeatmapInfo - { - OnlineBeatmapID = id * 10, - Version = "Normal", - StarDifficulty = 2, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = id * 10 + 1, - Version = "Hard", - StarDifficulty = 5, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 5, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = id * 10 + 2, - Version = "Insane", - StarDifficulty = 6, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 7, - } - }, - }), + Beatmaps = getBeatmaps(RNG.Next(1, 20)).ToList() }; } + private IEnumerable getBeatmaps(int count) + { + int id = 0; + + for (int i = 0; i < count; i++) + { + yield return new BeatmapInfo + { + OnlineBeatmapID = id++ * 10, + Version = "Normal", + StarDifficulty = RNG.NextSingle() * 10, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = RNG.NextSingle() * 10, + } + }; + } + } + private BeatmapSetInfo createTestBeatmapSetWithManyDifficulties(int id) { var toReturn = new BeatmapSetInfo From 0400b34349dd805750f78a8ac7b326c0a8299b88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:55:47 +0900 Subject: [PATCH 031/322] Load more components asynchronously after pool resolution --- osu.Game/Screens/Select/BeatmapCarousel.cs | 1 - .../Carousel/DrawableCarouselBeatmapSet.cs | 129 ++++++++++-------- 2 files changed, 70 insertions(+), 60 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a8212f7e59..00f62aa515 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -15,7 +15,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Threading; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 0bfec4f509..6717788506 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; - private Container beatmapContainer; + private Container beatmapContainer; private Bindable beatmapSetState; [Resolved] @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select.Carousel // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. AddRangeInternal(new Drawable[] { - beatmapContainer = new Container + beatmapContainer = new Container { X = 50, Y = MAX_HEIGHT, @@ -81,62 +81,68 @@ namespace osu.Game.Screens.Select.Carousel Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => - { - var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) - { - RelativeSizeAxes = Axes.Both, - }; - - background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); - - return background; - }, 300, 5000 - ), - new FillFlowContainer { - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { - new OsuSpriteText + RelativeSizeAxes = Axes.Both, + }; + + background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return background; + }, 300, 5000), + new DelayedLoadUnloadWrapper(() => + { + var mainFlow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), - Shadow = true, - }, - new OsuSpriteText - { - Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), - Shadow = true, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] + new OsuSpriteText { - new BeatmapSetOnlineStatusPill + Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + new OsuSpriteText + { + Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5 }, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Status = beatmapSet.Status - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(3), - ChildrenEnumerable = getDifficultyIcons(), - }, + new BeatmapSetOnlineStatusPill + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5 }, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapSet.Status + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), + ChildrenEnumerable = getDifficultyIcons(), + }, + } } } - } - } + }; + + mainFlow.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return mainFlow; + }, 100, 5000) }; beatmapContainer.Clear(); @@ -155,19 +161,24 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.Selected: - float yPos = 0; - var carouselBeatmapSet = (CarouselBeatmapSet)Item; - foreach (var item in carouselBeatmapSet.Children) + LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()), loaded => { - var beatmapPanel = item.CreateDrawableRepresentation(); + // make sure the pooled target hasn't changed. + if (carouselBeatmapSet != Item) + return; - beatmapPanel.Y = yPos; - yPos += item.TotalHeight; + float yPos = 0; - beatmapContainer.Add((DrawableCarouselBeatmap)beatmapPanel); - } + foreach (var item in loaded) + { + item.Y = yPos; + yPos += item.Item.TotalHeight; + + beatmapContainer.Add(item); + } + }); break; } From ca1f5dcada5affbb485c93c67716c400d8eb4f8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:11:41 +0900 Subject: [PATCH 032/322] Add back panel padding --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 00f62aa515..09c7322c58 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -676,6 +676,8 @@ namespace osu.Game.Screens.Select return set; } + private const float panel_padding = 5; + /// /// Computes the target Y positions for every item in the carousel. /// @@ -705,7 +707,7 @@ namespace osu.Game.Screens.Select // TODO: move this logic to DCBS too. // set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); // set.MoveToY(currentY, 750, Easing.OutExpo); - currentY += set.TotalHeight; + currentY += set.TotalHeight + panel_padding; break; } From 954d43ef56a67d01680fd6f551a78f0d3e2ee725 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:13:25 +0900 Subject: [PATCH 033/322] Debounce state application events --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4c4932c22a..4ba827de86 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -125,15 +125,15 @@ namespace osu.Game.Screens.Select.Carousel if (item == null) return; - ApplyState(); + Scheduler.AddOnce(ApplyState); Item.Filtered.ValueChanged += onStateChange; Item.State.ValueChanged += onStateChange; } - private void onStateChange(ValueChangedEvent obj) => Schedule(ApplyState); + private void onStateChange(ValueChangedEvent obj) => Scheduler.AddOnce(ApplyState); - private void onStateChange(ValueChangedEvent _) => Schedule(ApplyState); + private void onStateChange(ValueChangedEvent _) => Scheduler.AddOnce(ApplyState); protected virtual void ApplyState() { From 3cfc0dc82daf6846090eb4b1db81b12e8f8527d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:13:39 +0900 Subject: [PATCH 034/322] Add safeties to beatmap panel loading code --- .../Carousel/DrawableCarouselBeatmapSet.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 6717788506..f59a6a26e6 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -78,6 +79,9 @@ namespace osu.Game.Screens.Select.Carousel { base.UpdateItem(); + beatmapContainer.Clear(); + beatmapSetState?.UnbindAll(); + Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => @@ -145,8 +149,6 @@ namespace osu.Game.Screens.Select.Carousel }, 100, 5000) }; - beatmapContainer.Clear(); - beatmapSetState = Item.State.GetBoundCopy(); beatmapSetState.BindValueChanged(setSelected, true); } @@ -156,14 +158,16 @@ namespace osu.Game.Screens.Select.Carousel switch (obj.NewValue) { default: - beatmapContainer.Clear(); + foreach (var beatmap in beatmapContainer) + beatmap.FadeOut(50).Expire(); break; case CarouselItemState.Selected: var carouselBeatmapSet = (CarouselBeatmapSet)Item; - LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()), loaded => + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()).ToArray(), loaded => { // make sure the pooled target hasn't changed. if (carouselBeatmapSet != Item) @@ -175,9 +179,9 @@ namespace osu.Game.Screens.Select.Carousel { item.Y = yPos; yPos += item.Item.TotalHeight; - - beatmapContainer.Add(item); } + + beatmapContainer.ChildrenEnumerable = loaded; }); break; From 5c2f1346658f1a5a8d624da91040ba80db69b7f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:19:10 +0900 Subject: [PATCH 035/322] Add back left offset for selected set --- osu.Game/Screens/Select/BeatmapCarousel.cs | 10 ---------- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 7 ++++--- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 09c7322c58..956b2977bd 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -702,20 +702,10 @@ namespace osu.Game.Screens.Select { visibleItems.Add(set); yPositions.Add(currentY); - //lastSet = set; - // TODO: move this logic to DCBS too. - // set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - // set.MoveToY(currentY, 750, Easing.OutExpo); currentY += set.TotalHeight + panel_padding; break; } - - default: - continue; - // - // break; - // } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index f59a6a26e6..d92077cf36 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -153,9 +152,11 @@ namespace osu.Game.Screens.Select.Carousel beatmapSetState.BindValueChanged(setSelected, true); } - private void setSelected(ValueChangedEvent obj) + private void setSelected(ValueChangedEvent selected) { - switch (obj.NewValue) + BorderContainer.MoveToX(selected.NewValue == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + + switch (selected.NewValue) { default: foreach (var beatmap in beatmapContainer) From 5c29aa8cce966d27c92963ded66e48878eb9f70e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:19:20 +0900 Subject: [PATCH 036/322] Fix multiple difficulties being expanded at once --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4135960e06..656d1e39ba 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - BorderContainer.MoveToX(Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + BorderContainer.MoveToX(-50, 500, Easing.OutExpo); background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), @@ -185,6 +185,8 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); + BorderContainer.MoveToX(0, 500, Easing.OutExpo); + background.Colour = new Color4(20, 43, 51, 255); triangles.Colour = OsuColour.Gray(0.5f); } From c5a6f4b453f241b9269bc31fe183ae6888e2cf26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:32:29 +0900 Subject: [PATCH 037/322] Fix scroll to selected beatmap --- osu.Game/Screens/Select/BeatmapCarousel.cs | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 956b2977bd..9f5ee22106 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -604,12 +604,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). foreach (DrawableCarouselItem p in scrollableContent.Children) - { updateItem(p); - - // foreach (var pChild in p.ChildItems) - // updateItem(pChild, p); - } } protected override void UpdateAfterChildren() @@ -703,6 +698,26 @@ namespace osu.Game.Screens.Select visibleItems.Add(set); yPositions.Add(currentY); + if (item.State.Value == CarouselItemState.Selected) + { + // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space + // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas) + // then reapply the top semi-transparent area (because carousel's screen space starts below it) + // and finally add half of the panel's own height to achieve vertical centering of the panel itself + scrollTarget = currentY + DrawableCarouselBeatmapSet.HEIGHT - visibleHalfHeight + BleedTop; + + foreach (var b in set.Beatmaps) + { + if (b.State.Value == CarouselItemState.Selected) + { + scrollTarget += b.TotalHeight / 2; + break; + } + + scrollTarget += b.TotalHeight; + } + } + currentY += set.TotalHeight + panel_padding; break; } From 0a144a1388df5ac6849ffc7e4d31bf304c77cff2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:50:10 +0900 Subject: [PATCH 038/322] Correctly free panels after use to avoid finalizer disposal of subtree --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index d92077cf36..1cdc496af8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -53,6 +53,12 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapManager manager { get; set; } + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + Item = null; + } + [BackgroundDependencyLoader(true)] private void load(BeatmapSetOverlay beatmapOverlay) { @@ -81,6 +87,9 @@ namespace osu.Game.Screens.Select.Carousel beatmapContainer.Clear(); beatmapSetState?.UnbindAll(); + if (Item == null) + return; + Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => From 524419d5e4ebff561ed99d332e666732a6479eaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:02:08 +0900 Subject: [PATCH 039/322] Fix filtered items being considered for height calculation --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 15f622f3c4..7935debac7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count * DrawableCarouselBeatmap.HEIGHT; + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; default: return DrawableCarouselBeatmapSet.HEIGHT; From bb03c5d77c15d0b0230e2ce8ef1ec7f67d15a483 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:11:02 +0900 Subject: [PATCH 040/322] Temporarily disable masking temporarily to fix panels disappearing at extents --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9f5ee22106..20a36dbdc5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -571,7 +571,7 @@ namespace osu.Game.Screens.Select updateItems(); // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + //scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); // Find index range of all items that should be on-screen int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 1cdc496af8..daee7abcc0 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; @@ -31,6 +32,9 @@ namespace osu.Game.Screens.Select.Carousel { public const float HEIGHT = MAX_HEIGHT; + // TODO: don't do this. need to split out the base class' style so our height isn't fixed to the panel display height (and autosize?). + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + private Action restoreHiddenRequested; private Action viewDetails; From 15325f5f510da118d146fa7543b2dff3d4a1406e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:12:00 +0900 Subject: [PATCH 041/322] Base bounds checks on +1 (to avoid worrying about current item heights) --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 20a36dbdc5..3c3804389b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -574,11 +574,16 @@ namespace osu.Game.Screens.Select //scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); // Find index range of all items that should be on-screen - int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); + int firstIndex = yPositions.BinarySearch(visibleUpperBound); if (firstIndex < 0) firstIndex = ~firstIndex; int lastIndex = yPositions.BinarySearch(visibleBottomBound); if (lastIndex < 0) lastIndex = ~lastIndex; + // as we can't be 100% sure on the size of individual carousel drawables, + // always play it safe and extend bounds by one. + firstIndex = Math.Max(0, firstIndex - 1); + lastIndex = Math.Min(yPositions.Count - 1, lastIndex + 1); + if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { displayedRange = (firstIndex, lastIndex); From 8847cedf296f633008e3b7d8a38f90d8606f9fb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:55:17 +0900 Subject: [PATCH 042/322] Add initial pass of vertical transforms --- osu.Game/Screens/Select/BeatmapCarousel.cs | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3c3804389b..9c49f4bf67 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -566,12 +566,8 @@ namespace osu.Game.Screens.Select bool revalidateItems = !itemsCache.IsValid; - //todo: this should only refresh items, not everything here if (revalidateItems) - updateItems(); - - // Remove all items that should no longer be on-screen - //scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + updateYPositions(); // Find index range of all items that should be on-screen int firstIndex = yPositions.BinarySearch(visibleUpperBound); @@ -586,6 +582,9 @@ namespace osu.Game.Screens.Select if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); + displayedRange = (firstIndex, lastIndex); // Add those items within the previously found index range that should be displayed. @@ -598,11 +597,22 @@ namespace osu.Game.Screens.Select if (panel == null) { panel = setPool.Get(p => p.Item = item); + panel.Y = yPositions[i]; + panel.Depth = i; + + panel.ClearTransforms(); + scrollableContent.Add(panel); } + else + { + if (panel.IsPresent) + panel.MoveToY(yPositions[i], 800, Easing.OutQuint); + else + panel.Y = yPositions[i]; - panel.Y = yPositions[i]; - scrollableContent.ChangeChildDepth(panel, i); + scrollableContent.ChangeChildDepth(panel, i); + } } } @@ -682,7 +692,7 @@ namespace osu.Game.Screens.Select /// Computes the target Y positions for every item in the carousel. /// /// The Y position of the currently selected item. - private void updateItems() + private void updateYPositions() { yPositions.Clear(); visibleItems.Clear(); From 813ee1972895350f5ea563e5929acc4f5ca52659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:55:33 +0900 Subject: [PATCH 043/322] Use existing event flow for rendering beatmap difficulties --- .../Carousel/DrawableCarouselBeatmapSet.cs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index daee7abcc0..f03b6fff4a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -52,7 +52,6 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; private Container beatmapContainer; - private Bindable beatmapSetState; [Resolved] private BeatmapManager manager { get; set; } @@ -89,7 +88,6 @@ namespace osu.Game.Screens.Select.Carousel base.UpdateItem(); beatmapContainer.Clear(); - beatmapSetState?.UnbindAll(); if (Item == null) return; @@ -160,46 +158,48 @@ namespace osu.Game.Screens.Select.Carousel return mainFlow; }, 100, 5000) }; - - beatmapSetState = Item.State.GetBoundCopy(); - beatmapSetState.BindValueChanged(setSelected, true); } - private void setSelected(ValueChangedEvent selected) + protected override void Deselected() { - BorderContainer.MoveToX(selected.NewValue == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + base.Deselected(); - switch (selected.NewValue) + BorderContainer.MoveToX(0, 500, Easing.OutExpo); + + foreach (var beatmap in beatmapContainer) + beatmap.FadeOut(50).Expire(); + } + + protected override void Selected() + { + base.Selected(); + + BorderContainer.MoveToX(-100, 500, Easing.OutExpo); + + var carouselBeatmapSet = (CarouselBeatmapSet)Item; + + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + var visibleBeatmaps = carouselBeatmapSet.Children + .Where(c => c.Visible) + .Select(c => c.CreateDrawableRepresentation()) + .ToArray(); + + LoadComponentsAsync(visibleBeatmaps, loaded => { - default: - foreach (var beatmap in beatmapContainer) - beatmap.FadeOut(50).Expire(); - break; + // make sure the pooled target hasn't changed. + if (carouselBeatmapSet != Item) + return; - case CarouselItemState.Selected: + float yPos = 0; - var carouselBeatmapSet = (CarouselBeatmapSet)Item; + foreach (var item in loaded) + { + item.Y = yPos; + yPos += item.Item.TotalHeight; + } - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()).ToArray(), loaded => - { - // make sure the pooled target hasn't changed. - if (carouselBeatmapSet != Item) - return; - - float yPos = 0; - - foreach (var item in loaded) - { - item.Y = yPos; - yPos += item.Item.TotalHeight; - } - - beatmapContainer.ChildrenEnumerable = loaded; - }); - - break; - } + beatmapContainer.ChildrenEnumerable = loaded; + }); } private const int maximum_difficulty_icons = 18; From 82f9ca3de98d1cef76f64a6c67217384e4700460 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:02:06 +0900 Subject: [PATCH 044/322] Bind to filter event changes in base drawable item --- .../Screens/Select/Carousel/DrawableCarouselItem.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4ba827de86..8cf63d184c 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -40,6 +40,12 @@ namespace osu.Game.Screens.Select.Carousel { Item.Filtered.ValueChanged -= onStateChange; Item.State.ValueChanged -= onStateChange; + + if (item is CarouselGroup group) + { + foreach (var c in group.Children) + c.Filtered.ValueChanged -= onStateChange; + } } item = value; @@ -129,6 +135,12 @@ namespace osu.Game.Screens.Select.Carousel Item.Filtered.ValueChanged += onStateChange; Item.State.ValueChanged += onStateChange; + + if (Item is CarouselGroup group) + { + foreach (var c in group.Children) + c.Filtered.ValueChanged += onStateChange; + } } private void onStateChange(ValueChangedEvent obj) => Scheduler.AddOnce(ApplyState); From 220c8ba2c47a8f9ffc0df5cca1f1b465088c0838 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:02:45 +0900 Subject: [PATCH 045/322] Fix incorrect vertical offsets when difficulties are filtered away --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9c49f4bf67..79ad9c4584 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -723,6 +723,9 @@ namespace osu.Game.Screens.Select foreach (var b in set.Beatmaps) { + if (!b.Visible) + continue; + if (b.State.Value == CarouselItemState.Selected) { scrollTarget += b.TotalHeight / 2; From ce67f6508477ba96726bbb0c92e8397333fb030d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:11:10 +0900 Subject: [PATCH 046/322] Fix single results not showing up --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 79ad9c4584..9e295b6f53 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -578,7 +578,7 @@ namespace osu.Game.Screens.Select // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Min(yPositions.Count - 1, lastIndex + 1); + lastIndex = Math.Min(yPositions.Count, lastIndex + 1); if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { From fd8654cff3c9343a78cdda2749f5f5a26d293846 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:26:20 +0900 Subject: [PATCH 047/322] Add back difficulty panel spacing --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 4 +++- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9e295b6f53..04af07846f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -732,7 +732,7 @@ namespace osu.Game.Screens.Select break; } - scrollTarget += b.TotalHeight; + scrollTarget += b.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 7935debac7..f2f8cb9bd9 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * (DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING + DrawableCarouselBeatmap.HEIGHT); default: return DrawableCarouselBeatmapSet.HEIGHT; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 656d1e39ba..23404a6c6e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -31,7 +31,9 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { - public const float HEIGHT = MAX_HEIGHT * 0.6f; + public const float CAROUSEL_BEATMAP_SPACING = 5; + + public const float HEIGHT = MAX_HEIGHT * 0.6f; // TODO: add once base class is fixed + CAROUSEL_BEATMAP_SPACING; private readonly BeatmapInfo beatmap; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index f03b6fff4a..20e6258433 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -190,12 +190,12 @@ namespace osu.Game.Screens.Select.Carousel if (carouselBeatmapSet != Item) return; - float yPos = 0; + float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; foreach (var item in loaded) { item.Y = yPos; - yPos += item.Item.TotalHeight; + yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } beatmapContainer.ChildrenEnumerable = loaded; From 975cd5a84021b5320207e0bfb7ce0d46c6757aa5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 12:47:12 +0900 Subject: [PATCH 048/322] Add back beatmap difficulty appear/disappear movement --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 20e6258433..660d5d5b31 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -167,7 +167,10 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.MoveToX(0, 500, Easing.OutExpo); foreach (var beatmap in beatmapContainer) - beatmap.FadeOut(50).Expire(); + { + beatmap.MoveToY(0, 800, Easing.OutQuint); + beatmap.FadeOut(80).Expire(); + } } protected override void Selected() @@ -176,6 +179,9 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.MoveToX(-100, 500, Easing.OutExpo); + // on selection we show our child beatmaps. + // for now this is a simple drawable construction each selection. + // can be improved in the future. var carouselBeatmapSet = (CarouselBeatmapSet)Item; // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 @@ -190,15 +196,15 @@ namespace osu.Game.Screens.Select.Carousel if (carouselBeatmapSet != Item) return; + beatmapContainer.ChildrenEnumerable = loaded; + float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; foreach (var item in loaded) { - item.Y = yPos; + item.MoveToY(yPos, 800, Easing.OutQuint); yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } - - beatmapContainer.ChildrenEnumerable = loaded; }); } From 9814e9ba7f098a4dfd78bb6ae3770e20c87592ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 12:50:39 +0900 Subject: [PATCH 049/322] Split classes out to reduce loc burder on DrawableCarouselBeatmapSet --- .../SongSelect/TestScenePlaySongSelect.cs | 22 ++-- .../Carousel/DrawableCarouselBeatmapSet.cs | 117 ------------------ .../Carousel/FilterableDifficultyIcon.cs | 32 +++++ .../FilterableGroupedDifficultyIcon.cs | 38 ++++++ .../Select/Carousel/PanelBackground.cs | 69 +++++++++++ 5 files changed, 150 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs create mode 100644 osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs create mode 100644 osu.Game/Screens/Select/Carousel/PanelBackground.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0299b7a084..cd97ffe9e7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -507,7 +507,7 @@ namespace osu.Game.Tests.Visual.SongSelect var selectedPanel = songSelect.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected); // special case for converts checked here. - return selectedPanel.ChildrenOfType().All(i => + return selectedPanel.ChildrenOfType().All(i => i.IsFiltered || i.Item.Beatmap.Ruleset.ID == targetRuleset || i.Item.Beatmap.Ruleset.ID == 0); }); @@ -606,10 +606,10 @@ namespace osu.Game.Tests.Visual.SongSelect set = songSelect.Carousel.ChildrenOfType().First(); }); - DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon difficultyIcon = null; AddStep("Find an icon", () => { - difficultyIcon = set.ChildrenOfType() + difficultyIcon = set.ChildrenOfType() .First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex()); }); @@ -634,13 +634,13 @@ namespace osu.Game.Tests.Visual.SongSelect })); BeatmapInfo filteredBeatmap = null; - DrawableCarouselBeatmapSet.FilterableDifficultyIcon filteredIcon = null; + FilterableDifficultyIcon filteredIcon = null; AddStep("Get filtered icon", () => { filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First(b => b.BPM < maxBPM); int filteredBeatmapIndex = getBeatmapIndex(filteredBeatmap.BeatmapSet, filteredBeatmap); - filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); + filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); }); AddStep("Click on a filtered difficulty", () => @@ -674,10 +674,10 @@ namespace osu.Game.Tests.Visual.SongSelect return set != null; }); - DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon difficultyIcon = null; AddStep("Find an icon for different ruleset", () => { - difficultyIcon = set.ChildrenOfType() + difficultyIcon = set.ChildrenOfType() .First(icon => icon.Item.Beatmap.Ruleset.ID == 3); }); @@ -725,10 +725,10 @@ namespace osu.Game.Tests.Visual.SongSelect return set != null; }); - DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon groupIcon = null; + FilterableGroupedDifficultyIcon groupIcon = null; AddStep("Find group icon for different ruleset", () => { - groupIcon = set.ChildrenOfType() + groupIcon = set.ChildrenOfType() .First(icon => icon.Items.First().Beatmap.Ruleset.ID == 3); }); @@ -821,9 +821,9 @@ namespace osu.Game.Tests.Visual.SongSelect private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap); - private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, DrawableCarouselBeatmapSet.FilterableDifficultyIcon icon) + private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) { - return set.ChildrenOfType().ToList().FindIndex(i => i == icon); + return set.ChildrenOfType().ToList().FindIndex(i => i == icon); } private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 660d5d5b31..8ab7a054ff 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -5,15 +5,11 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -22,9 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { @@ -285,116 +279,5 @@ namespace osu.Game.Screens.Select.Carousel State = { Value = state } }; } - - private class PanelBackground : BufferedContainer - { - public PanelBackground(WorkingBeatmap working) - { - CacheDrawnFrameBuffer = true; - RedrawOnScale = false; - - Children = new Drawable[] - { - new BeatmapBackgroundSprite(working) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, - new FillFlowContainer - { - Depth = -1, - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle - Shear = new Vector2(0.8f, 0), - Alpha = 0.5f, - Children = new[] - { - // The left half with no gradient applied - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Width = 0.4f, - }, - // Piecewise-linear gradient with 3 segments to make it appear smoother - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), - Width = 0.05f, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), - Width = 0.2f, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), - Width = 0.05f, - }, - } - }, - }; - } - } - - public class FilterableDifficultyIcon : DifficultyIcon - { - private readonly BindableBool filtered = new BindableBool(); - - public bool IsFiltered => filtered.Value; - - public readonly CarouselBeatmap Item; - - public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.Beatmap) - { - filtered.BindTo(item.Filtered); - filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); - filtered.TriggerChange(); - - Item = item; - } - - protected override bool OnClick(ClickEvent e) - { - Item.State.Value = CarouselItemState.Selected; - return true; - } - } - - public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon - { - public readonly List Items; - - public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) - : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) - { - Items = items; - - foreach (var item in items) - item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay)); - - updateFilteredDisplay(); - } - - protected override bool OnClick(ClickEvent e) - { - Items.First().State.Value = CarouselItemState.Selected; - return true; - } - - private void updateFilteredDisplay() - { - // for now, fade the whole group based on the ratio of hidden items. - this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100); - } - } } } diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs new file mode 100644 index 0000000000..591e9fea22 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -0,0 +1,32 @@ +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Drawables; + +namespace osu.Game.Screens.Select.Carousel +{ + public class FilterableDifficultyIcon : DifficultyIcon + { + private readonly BindableBool filtered = new BindableBool(); + + public bool IsFiltered => filtered.Value; + + public readonly CarouselBeatmap Item; + + public FilterableDifficultyIcon(CarouselBeatmap item) + : base(item.Beatmap) + { + filtered.BindTo(item.Filtered); + filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); + filtered.TriggerChange(); + + Item = item; + } + + protected override bool OnClick(ClickEvent e) + { + Item.State.Value = CarouselItemState.Selected; + return true; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs new file mode 100644 index 0000000000..73b5781a37 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Rulesets; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select.Carousel +{ + public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon + { + public readonly List Items; + + public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) + : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) + { + Items = items; + + foreach (var item in items) + item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay)); + + updateFilteredDisplay(); + } + + protected override bool OnClick(ClickEvent e) + { + Items.First().State.Value = CarouselItemState.Selected; + return true; + } + + private void updateFilteredDisplay() + { + // for now, fade the whole group based on the ratio of hidden items. + this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100); + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Select/Carousel/PanelBackground.cs b/osu.Game/Screens/Select/Carousel/PanelBackground.cs new file mode 100644 index 0000000000..587aa0a74e --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/PanelBackground.cs @@ -0,0 +1,69 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select.Carousel +{ + public class PanelBackground : BufferedContainer + { + public PanelBackground(WorkingBeatmap working) + { + CacheDrawnFrameBuffer = true; + RedrawOnScale = false; + + Children = new Drawable[] + { + new BeatmapBackgroundSprite(working) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, + new FillFlowContainer + { + Depth = -1, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle + Shear = new Vector2(0.8f, 0), + Alpha = 0.5f, + Children = new[] + { + // The left half with no gradient applied + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Width = 0.4f, + }, + // Piecewise-linear gradient with 3 segments to make it appear smoother + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), + Width = 0.05f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), + Width = 0.2f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), + Width = 0.05f, + }, + } + }, + }; + } + } +} From b92c22ad42af399e43a40bfe6703790a4ce75ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:11:47 +0900 Subject: [PATCH 050/322] Add logging --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++++ .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 04af07846f..78012e8bfd 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -18,6 +18,7 @@ using osu.Framework.Threading; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Graphics.Containers; @@ -582,6 +583,8 @@ namespace osu.Game.Screens.Select if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { + Logger.Log("revalidation requested"); + // Remove all items that should no longer be on-screen scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); @@ -596,6 +599,7 @@ namespace osu.Game.Screens.Select if (panel == null) { + Logger.Log($"getting panel for {item} from pool"); panel = setPool.Get(p => p.Item = item); panel.Y = yPositions[i]; panel.Depth = i; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8ab7a054ff..fed32b5849 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; @@ -86,10 +87,12 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; + Logger.Log($"updating item {beatmapSet}"); Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { + Logger.Log($"loaded background item {beatmapSet}"); var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, From f6aa448523b5e5fafe2c694d215671c08cd431df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:21:21 +0900 Subject: [PATCH 051/322] Store y positions inside items rather than in a separate array --- osu.Game/Screens/Select/BeatmapCarousel.cs | 54 +++++++++++++------ .../Screens/Select/Carousel/CarouselItem.cs | 10 +++- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 78012e8bfd..87e930fe8a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -116,7 +116,6 @@ namespace osu.Game.Screens.Select }); } - private readonly List yPositions = new List(); private readonly List visibleItems = new List(); private readonly Cached itemsCache = new Cached(); @@ -570,23 +569,15 @@ namespace osu.Game.Screens.Select if (revalidateItems) updateYPositions(); - // Find index range of all items that should be on-screen - int firstIndex = yPositions.BinarySearch(visibleUpperBound); - if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(visibleBottomBound); - if (lastIndex < 0) lastIndex = ~lastIndex; - - // as we can't be 100% sure on the size of individual carousel drawables, - // always play it safe and extend bounds by one. - firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Min(yPositions.Count, lastIndex + 1); + var (firstIndex, lastIndex) = getDisplayRange(); if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { Logger.Log("revalidation requested"); // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); + // TODO: figure out a more resilient way of doing this removal. + // scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); displayedRange = (firstIndex, lastIndex); @@ -601,7 +592,7 @@ namespace osu.Game.Screens.Select { Logger.Log($"getting panel for {item} from pool"); panel = setPool.Get(p => p.Item = item); - panel.Y = yPositions[i]; + panel.Y = item.CarouselYPosition; panel.Depth = i; panel.ClearTransforms(); @@ -611,9 +602,9 @@ namespace osu.Game.Screens.Select else { if (panel.IsPresent) - panel.MoveToY(yPositions[i], 800, Easing.OutQuint); + panel.MoveToY(item.CarouselYPosition, 800, Easing.OutQuint); else - panel.Y = yPositions[i]; + panel.Y = item.CarouselYPosition; scrollableContent.ChangeChildDepth(panel, i); } @@ -626,6 +617,22 @@ namespace osu.Game.Screens.Select updateItem(p); } + private (int firstIndex, int lastIndex) getDisplayRange() + { + // Find index range of all items that should be on-screen + // TODO: reduce allocs of CarouselBoundsItem. + int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound)); + if (firstIndex < 0) firstIndex = ~firstIndex; + int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound)); + if (lastIndex < 0) lastIndex = ~lastIndex; + + // as we can't be 100% sure on the size of individual carousel drawables, + // always play it safe and extend bounds by one. + firstIndex = Math.Max(0, firstIndex - 1); + lastIndex = Math.Min(visibleItems.Count, lastIndex + 1); + return (firstIndex, lastIndex); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -698,7 +705,6 @@ namespace osu.Game.Screens.Select /// The Y position of the currently selected item. private void updateYPositions() { - yPositions.Clear(); visibleItems.Clear(); float currentY = visibleHalfHeight; @@ -715,7 +721,7 @@ namespace osu.Game.Screens.Select case CarouselBeatmapSet set: { visibleItems.Add(set); - yPositions.Add(currentY); + set.CarouselYPosition = currentY; if (item.State.Value == CarouselItemState.Selected) { @@ -815,6 +821,20 @@ namespace osu.Game.Screens.Select p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } + /// + /// A carousel item strictly used for binary search purposes. + /// + private class CarouselBoundsItem : CarouselItem + { + public CarouselBoundsItem(in float pos) + { + CarouselYPosition = pos; + } + + public override DrawableCarouselItem CreateDrawableRepresentation() => + throw new NotImplementedException(); + } + private class CarouselRoot : CarouselGroupEagerSelect { private readonly BeatmapCarousel carousel; diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 004d9779ef..4bd477412d 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -1,14 +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 osu.Framework.Bindables; namespace osu.Game.Screens.Select.Carousel { - public abstract class CarouselItem + public abstract class CarouselItem : IComparable { public virtual float TotalHeight => 0; + /// + /// An externally defined value used to determine this item's vertical display offset relative to the carousel. + /// + public float CarouselYPosition; + public readonly BindableBool Filtered = new BindableBool(); public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); @@ -42,6 +48,8 @@ namespace osu.Game.Screens.Select.Carousel } public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ChildID.CompareTo(other.ChildID); + + public int CompareTo(CarouselItem other) => CarouselYPosition.CompareTo(other.CarouselYPosition); } public enum CarouselItemState From 20b54fb904957a208adb2fdbdfb9351f6a3ebe73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:25:45 +0900 Subject: [PATCH 052/322] Move transform clean-up to pooling free call --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 -- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 87e930fe8a..c15652b132 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -595,8 +595,6 @@ namespace osu.Game.Screens.Select panel.Y = item.CarouselYPosition; panel.Depth = i; - panel.ClearTransforms(); - scrollableContent.Add(panel); } else diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fed32b5849..635eb6f375 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -54,7 +54,9 @@ namespace osu.Game.Screens.Select.Carousel protected override void FreeAfterUse() { base.FreeAfterUse(); + Item = null; + ClearTransforms(); } [BackgroundDependencyLoader(true)] From 06e84c8eb3137c451fa0595fb5b55e92cbb9368f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:44:32 +0900 Subject: [PATCH 053/322] Add comments and split out update steps into a more logical flow --- osu.Game/Screens/Select/BeatmapCarousel.cs | 57 ++++++++++++---------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c15652b132..ad2c4c2ca8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -566,53 +566,56 @@ namespace osu.Game.Screens.Select bool revalidateItems = !itemsCache.IsValid; + // First we iterate over all non-filtered carousel items and populate their + // vertical position data. if (revalidateItems) updateYPositions(); - var (firstIndex, lastIndex) = getDisplayRange(); + // This data is consumed to find the currently displayable range. + // This is the range we want to keep drawables for, and should exceed the visible range slightly to avoid drawable churn. + var newDisplayRange = getDisplayRange(); - if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) + // If the filtered items or visible range has changed, pooling requirements need to be checked. + // This involves fetching new items from the pool, returning no-longer required items. + if (revalidateItems || newDisplayRange != displayedRange) { Logger.Log("revalidation requested"); - // Remove all items that should no longer be on-screen - // TODO: figure out a more resilient way of doing this removal. - // scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); - - displayedRange = (firstIndex, lastIndex); + displayedRange = newDisplayRange; // Add those items within the previously found index range that should be displayed. - for (int i = firstIndex; i < lastIndex; ++i) + for (int i = displayedRange.first; i < displayedRange.last; ++i) { var item = visibleItems[i]; - var panel = scrollableContent.FirstOrDefault(c => c.Item == item); + if (scrollableContent.Any(c => c.Item == item)) continue; - if (panel == null) - { - Logger.Log($"getting panel for {item} from pool"); - panel = setPool.Get(p => p.Item = item); - panel.Y = item.CarouselYPosition; - panel.Depth = i; + Logger.Log($"getting panel for {item} from pool"); + var panel = setPool.Get(p => p.Item = item); + panel.Depth = i; + panel.Y = item.CarouselYPosition; + scrollableContent.Add(panel); + } - scrollableContent.Add(panel); - } - else - { - if (panel.IsPresent) - panel.MoveToY(item.CarouselYPosition, 800, Easing.OutQuint); - else - panel.Y = item.CarouselYPosition; + // Remove any items which are far out of the visible range. + } - scrollableContent.ChangeChildDepth(panel, i); - } + // Finally, if the filtered items have changed, animate drawables to their new locations. + // This is common if a selected/collapsed state has changed. + if (revalidateItems) + { + foreach (DrawableCarouselItem panel in scrollableContent.Children) + { + panel.MoveToY(panel.Item.CarouselYPosition, 800, Easing.OutQuint); } } - // Update externally controlled state of currently visible items - // (e.g. x-offset and opacity). + // Update externally controlled state of currently visible items (e.g. x-offset and opacity). + // This is a per-frame update on all drawable panels. foreach (DrawableCarouselItem p in scrollableContent.Children) + { updateItem(p); + } } private (int firstIndex, int lastIndex) getDisplayRange() From 29983afceffd4f44d91d114b49929cd4b27b584a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:51:27 +0900 Subject: [PATCH 054/322] Replace pool/cleanup logic with simplest form possible This will temporarily break panels that go off-screen, as they will disappear immediately --- osu.Game/Screens/Select/BeatmapCarousel.cs | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ad2c4c2ca8..56fe28fd14 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -583,21 +583,31 @@ namespace osu.Game.Screens.Select displayedRange = newDisplayRange; - // Add those items within the previously found index range that should be displayed. - for (int i = displayedRange.first; i < displayedRange.last; ++i) + var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); + + foreach (var panel in scrollableContent.Children) { - var item = visibleItems[i]; + if (toDisplay.Remove(panel.Item)) + { + // panel already displayed. + continue; + } - if (scrollableContent.Any(c => c.Item == item)) continue; - - Logger.Log($"getting panel for {item} from pool"); - var panel = setPool.Get(p => p.Item = item); - panel.Depth = i; - panel.Y = item.CarouselYPosition; - scrollableContent.Add(panel); + // panel displayed but not required + scrollableContent.Remove(panel); } - // Remove any items which are far out of the visible range. + // Add those items within the previously found index range that should be displayed. + foreach (var item in toDisplay) + { + Logger.Log($"getting panel for {item} from pool"); + var panel = setPool.Get(p => p.Item = item); + + panel.Depth = item.CarouselYPosition; // todo: i think this is correct + panel.Y = item.CarouselYPosition; + + scrollableContent.Add(panel); + } } // Finally, if the filtered items have changed, animate drawables to their new locations. From 075bf23714fe358b09481a7bab008042e4f3fa8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 14:14:39 +0900 Subject: [PATCH 055/322] Better track off-screen drawables (and return to pool less often) --- osu.Game/Screens/Select/BeatmapCarousel.cs | 24 ++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 56fe28fd14..c8ba98f6be 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -560,6 +560,16 @@ namespace osu.Game.Screens.Select private (int first, int last) displayedRange; + /// + /// Extend the range to retain already loaded pooled drawables. + /// + private const float distance_offscreen_before_unload = 1024; + + /// + /// Extend the range to update positions / retrieve pooled drawables outside of visible range. + /// + private const float distance_offscreen_to_preload = 256; + protected override void Update() { base.Update(); @@ -585,7 +595,7 @@ namespace osu.Game.Screens.Select var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); - foreach (var panel in scrollableContent.Children) + foreach (var panel in scrollableContent.Children.ToArray()) { if (toDisplay.Remove(panel.Item)) { @@ -593,8 +603,10 @@ namespace osu.Game.Screens.Select continue; } - // panel displayed but not required - scrollableContent.Remove(panel); + // panel loaded as drawable but not required by visible range. + // remove but only if too far off-screen + if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + scrollableContent.Remove(panel); } // Add those items within the previously found index range that should be displayed. @@ -603,7 +615,7 @@ namespace osu.Game.Screens.Select Logger.Log($"getting panel for {item} from pool"); var panel = setPool.Get(p => p.Item = item); - panel.Depth = item.CarouselYPosition; // todo: i think this is correct + panel.Depth = item.CarouselYPosition; panel.Y = item.CarouselYPosition; scrollableContent.Add(panel); @@ -632,9 +644,9 @@ namespace osu.Game.Screens.Select { // Find index range of all items that should be on-screen // TODO: reduce allocs of CarouselBoundsItem. - int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound)); + int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound - distance_offscreen_to_preload)); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound)); + int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound + distance_offscreen_to_preload)); if (lastIndex < 0) lastIndex = ~lastIndex; // as we can't be 100% sure on the size of individual carousel drawables, From 1b7e3397c6df1e04233d3c37e3c83d9b4aeb90b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 14:23:29 +0900 Subject: [PATCH 056/322] Use expiry to avoid ToArray --- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c8ba98f6be..65bd607f85 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -595,7 +595,7 @@ namespace osu.Game.Screens.Select var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); - foreach (var panel in scrollableContent.Children.ToArray()) + foreach (var panel in scrollableContent.Children) { if (toDisplay.Remove(panel.Item)) { @@ -606,7 +606,11 @@ namespace osu.Game.Screens.Select // panel loaded as drawable but not required by visible range. // remove but only if too far off-screen if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) - scrollableContent.Remove(panel); + { + // todo: may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + panel.ClearTransforms(); + panel.Expire(); + } } // Add those items within the previously found index range that should be displayed. From 2aad482545ce8a95fe3de3630278ddaf4d39b048 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 14:37:44 +0900 Subject: [PATCH 057/322] Fix x offsets of difficulties not being updated --- osu.Game/Screens/Select/BeatmapCarousel.cs | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 65bd607f85..775b4ad7b6 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -638,9 +638,15 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. - foreach (DrawableCarouselItem p in scrollableContent.Children) + foreach (DrawableCarouselItem item in scrollableContent.Children) { - updateItem(p); + updateItem(item); + + if (item is DrawableCarouselBeatmapSet set) + { + foreach (var diff in set.ChildItems) + updateItem(diff, item); + } } } @@ -831,21 +837,20 @@ namespace osu.Game.Screens.Select /// Update a item's x position and multiplicative alpha based on its y position and /// the current scroll position. /// - /// The item to be updated. - private void updateItem(DrawableCarouselItem p) + /// The item to be updated. + /// For nested items, the parent of the item to be updated. + private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) { - float itemDrawY = p.Position.Y - visibleUpperBound + p.DrawHeight / 2; + Vector2 posInScroll = scrollableContent.ToLocalSpace(item.ScreenSpaceDrawQuad.Centre); + float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); - // Setting the origin position serves as an additive position on top of potential - // local transformation we may want to apply (e.g. when a item gets selected, we - // may want to smoothly transform it leftwards.) - p.OriginPosition = new Vector2(-offsetX(dist, visibleHalfHeight), 0); + item.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can - // layer transformations on top, with a similar reasoning to the previous comment. - p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); + // layer alpha transformations on top. + item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } /// From cfec4f4fc14833562f464ec79eea0d4ee9f65549 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:19:32 +0900 Subject: [PATCH 058/322] Extract header element from base DrawableCarouselItem class --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/CarouselHeader.cs | 108 +++++++++++++++++ .../Carousel/DrawableCarouselBeatmap.cs | 11 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 20 ++-- .../Select/Carousel/DrawableCarouselItem.cs | 113 +++++------------- 5 files changed, 152 insertions(+), 102 deletions(-) create mode 100644 osu.Game/Screens/Select/Carousel/CarouselHeader.cs diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 775b4ad7b6..bcdbc53e26 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -841,7 +841,7 @@ namespace osu.Game.Screens.Select /// For nested items, the parent of the item to be updated. private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) { - Vector2 posInScroll = scrollableContent.ToLocalSpace(item.ScreenSpaceDrawQuad.Centre); + Vector2 posInScroll = scrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs new file mode 100644 index 0000000000..f59cccd8b6 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.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.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +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.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select.Carousel +{ + public class CarouselHeader : Container + { + private SampleChannel sampleHover; + + private readonly Box hoverLayer; + + public Container BorderContainer; + + public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); + + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + + public CarouselHeader() + { + RelativeSizeAxes = Axes.X; + Height = DrawableCarouselItem.MAX_HEIGHT; + + InternalChild = BorderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + BorderColour = new Color4(221, 255, 255, 255), + Children = new Drawable[] + { + Content, + hoverLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); + hoverLayer.Colour = colours.Blue.Opacity(0.1f); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + State.BindValueChanged(updateState, true); + } + + private void updateState(ValueChangedEvent state) + { + switch (state.NewValue) + { + case CarouselItemState.Collapsed: + case CarouselItemState.NotSelected: + BorderContainer.BorderThickness = 0; + BorderContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, Colour = Color4.Black.Opacity(100), + }; + break; + + case CarouselItemState.Selected: + BorderContainer.BorderThickness = 2.5f; + BorderContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), Radius = 20, Roundness = 10, + }; + break; + } + } + + protected override bool OnHover(HoverEvent e) + { + sampleHover?.Play(); + + hoverLayer.FadeIn(100, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverLayer.FadeOut(1000, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 23404a6c6e..b06b60e6c5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -64,15 +64,14 @@ namespace osu.Game.Screens.Select.Carousel public DrawableCarouselBeatmap(CarouselBeatmap panel) { beatmap = panel.Beatmap; - Height = HEIGHT; - - // todo: temporary Item = panel; } [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { + Header.Height = HEIGHT; + if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); @@ -83,7 +82,7 @@ namespace osu.Game.Screens.Select.Carousel if (manager != null) hideRequested = manager.Hide; - Content.Children = new Drawable[] + Header.Children = new Drawable[] { background = new Box { @@ -174,7 +173,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - BorderContainer.MoveToX(-50, 500, Easing.OutExpo); + Header.MoveToX(-50, 500, Easing.OutExpo); background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), @@ -187,7 +186,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); - BorderContainer.MoveToX(0, 500, Easing.OutExpo); + Header.MoveToX(0, 500, Easing.OutExpo); background.Colour = new Color4(20, 43, 51, 255); triangles.Colour = OsuColour.Gray(0.5f); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 635eb6f375..e1a2ce3962 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -67,16 +67,12 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; - // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. - AddRangeInternal(new Drawable[] + Content.Add(beatmapContainer = new Container { - beatmapContainer = new Container - { - X = 50, - Y = MAX_HEIGHT, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, + X = 50, + Y = MAX_HEIGHT, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, }); } @@ -90,7 +86,7 @@ namespace osu.Game.Screens.Select.Carousel return; Logger.Log($"updating item {beatmapSet}"); - Content.Children = new Drawable[] + Header.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { @@ -163,7 +159,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); - BorderContainer.MoveToX(0, 500, Easing.OutExpo); + MovementContainer.MoveToX(0, 500, Easing.OutExpo); foreach (var beatmap in beatmapContainer) { @@ -176,7 +172,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - BorderContainer.MoveToX(-100, 500, Easing.OutExpo); + MovementContainer.MoveToX(-100, 500, Easing.OutExpo); // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 8cf63d184c..6a9b60fa57 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -4,21 +4,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Graphics; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { @@ -28,6 +18,14 @@ namespace osu.Game.Screens.Select.Carousel public override bool IsPresent => base.IsPresent || Item?.Visible == true; + public readonly CarouselHeader Header; + + protected readonly Container Content; + + protected readonly Container MovementContainer; + + private CarouselItem item; + public CarouselItem Item { get => item; @@ -38,8 +36,10 @@ namespace osu.Game.Screens.Select.Carousel if (item != null) { - Item.Filtered.ValueChanged -= onStateChange; - Item.State.ValueChanged -= onStateChange; + item.Filtered.ValueChanged -= onStateChange; + item.State.ValueChanged -= onStateChange; + + Header.State.UnbindFrom(item.State); if (item is CarouselGroup group) { @@ -57,67 +57,33 @@ namespace osu.Game.Screens.Select.Carousel public virtual IEnumerable ChildItems => Enumerable.Empty(); - private readonly Container nestedContainer; - - protected readonly Container BorderContainer; - - private readonly Box hoverLayer; - - protected Container Content => nestedContainer; - protected DrawableCarouselItem() { - Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Alpha = 0; - InternalChild = BorderContainer = new Container + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - BorderColour = new Color4(221, 255, 255, 255), - Children = new Drawable[] + MovementContainer = new Container { - nestedContainer = new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - }, - hoverLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, - } + Header = new CarouselHeader(), + Content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + }, }; } - private SampleChannel sampleHover; - private CarouselItem item; - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { - sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); - hoverLayer.Colour = colours.Blue.Opacity(0.1f); - } - - protected override bool OnHover(HoverEvent e) - { - sampleHover?.Play(); - - hoverLayer.FadeIn(100, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverLayer.FadeOut(1000, Easing.OutQuint); - base.OnHoverLost(e); - } - - public void SetMultiplicativeAlpha(float alpha) => BorderContainer.Alpha = alpha; + public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; protected override void LoadComplete() { @@ -136,6 +102,8 @@ namespace osu.Game.Screens.Select.Carousel Item.Filtered.ValueChanged += onStateChange; Item.State.ValueChanged += onStateChange; + Header.State.BindTo(Item.State); + if (Item is CarouselGroup group) { foreach (var c in group.Children) @@ -171,31 +139,10 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void Selected() { Debug.Assert(Item != null); - - Item.State.Value = CarouselItemState.Selected; - - BorderContainer.BorderThickness = 2.5f; - BorderContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(130, 204, 255, 150), - Radius = 20, - Roundness = 10, - }; } protected virtual void Deselected() { - Item.State.Value = CarouselItemState.NotSelected; - - BorderContainer.BorderThickness = 0; - BorderContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(1), - Radius = 10, - Colour = Color4.Black.Opacity(100), - }; } protected override bool OnClick(ClickEvent e) From dd8943eb7f5f3816fc2f3b1d218a41166e373b78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:19:41 +0900 Subject: [PATCH 059/322] Update test scene to fix crash --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 7d850a0d13..d3ae3a19ea 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -427,7 +427,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = createTestBeatmapSet(i); + var set = createTestBeatmapSet(i, 3); set.Beatmaps[0].StarDifficulty = 3 - i; set.Beatmaps[2].StarDifficulty = 6 + i; sets.Add(set); @@ -822,7 +822,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection is visible", selectedBeatmapVisible); } - private BeatmapSetInfo createTestBeatmapSet(int id) + private BeatmapSetInfo createTestBeatmapSet(int id, int minimumDifficulties = 1) { return new BeatmapSetInfo { @@ -836,7 +836,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"test set #{id}!", AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) }, - Beatmaps = getBeatmaps(RNG.Next(1, 20)).ToList() + Beatmaps = getBeatmaps(RNG.Next(minimumDifficulties, 20)).ToList() }; } From c08b5e8d03b83221ac0c56688d42374262f9c6ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:23:27 +0900 Subject: [PATCH 060/322] Align beatmap difficulties correctly --- 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 e1a2ce3962..64a990a27d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select.Carousel Content.Add(beatmapContainer = new Container { - X = 50, + X = 100, Y = MAX_HEIGHT, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 1da49073abd01ec14b84b3e32c9fa353eae7d738 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:32:37 +0900 Subject: [PATCH 061/322] Calculate content height automatically --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 1 - osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 64a990a27d..b83915e0c6 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -70,7 +70,6 @@ namespace osu.Game.Screens.Select.Carousel Content.Add(beatmapContainer = new Container { X = 100, - Y = MAX_HEIGHT, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 6a9b60fa57..5bcaffa3cf 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -20,6 +20,9 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselHeader Header; + /// + /// Optional content which sits below the header. + /// protected readonly Container Content; protected readonly Container MovementContainer; @@ -89,6 +92,9 @@ namespace osu.Game.Screens.Select.Carousel { base.LoadComplete(); + // avoid using fill flow for performance reasons. header size doesn't change after load. + Content.Y = Header.Height; + UpdateItem(); } From cecdf14f5363d9590b130526a8e24e8c3bbddefd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 16:04:37 +0900 Subject: [PATCH 062/322] Avoid reconstructing beatmap difficulties that were recently displayed --- .../Carousel/DrawableCarouselBeatmapSet.cs | 78 ++++++++++++------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b83915e0c6..9a822f83e3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -56,6 +56,7 @@ namespace osu.Game.Screens.Select.Carousel base.FreeAfterUse(); Item = null; + ClearTransforms(); } @@ -66,20 +67,14 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; - - Content.Add(beatmapContainer = new Container - { - X = 100, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }); } protected override void UpdateItem() { base.UpdateItem(); - beatmapContainer.Clear(); + Content.Clear(); + beatmapContainer = null; if (Item == null) return; @@ -160,10 +155,13 @@ namespace osu.Game.Screens.Select.Carousel MovementContainer.MoveToX(0, 500, Easing.OutExpo); - foreach (var beatmap in beatmapContainer) + if (beatmapContainer != null) { - beatmap.MoveToY(0, 800, Easing.OutQuint); - beatmap.FadeOut(80).Expire(); + foreach (var beatmap in beatmapContainer) + { + beatmap.MoveToY(0, 800, Easing.OutQuint); + beatmap.FadeOut(80); + } } } @@ -173,33 +171,55 @@ namespace osu.Game.Screens.Select.Carousel MovementContainer.MoveToX(-100, 500, Easing.OutExpo); - // on selection we show our child beatmaps. - // for now this is a simple drawable construction each selection. - // can be improved in the future. - var carouselBeatmapSet = (CarouselBeatmapSet)Item; - - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - var visibleBeatmaps = carouselBeatmapSet.Children - .Where(c => c.Visible) - .Select(c => c.CreateDrawableRepresentation()) - .ToArray(); - - LoadComponentsAsync(visibleBeatmaps, loaded => + if (beatmapContainer != null) { - // make sure the pooled target hasn't changed. - if (carouselBeatmapSet != Item) - return; + // if already loaded, we only need to re-animate. + animateBeatmaps(); + } + else + { + // on selection we show our child beatmaps. + // for now this is a simple drawable construction each selection. + // can be improved in the future. + var carouselBeatmapSet = (CarouselBeatmapSet)Item; - beatmapContainer.ChildrenEnumerable = loaded; + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + var visibleBeatmaps = carouselBeatmapSet.Children + .Where(c => c.Visible) + .Select(c => c.CreateDrawableRepresentation()) + .ToArray(); + beatmapContainer = new Container + { + X = 100, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ChildrenEnumerable = visibleBeatmaps + }; + + Logger.Log($"loading {visibleBeatmaps.Length} beatmaps for {Item}"); + + LoadComponentAsync(beatmapContainer, loaded => + { + // make sure the pooled target hasn't changed. + if (carouselBeatmapSet != Item) + return; + + Content.Child = loaded; + animateBeatmaps(); + }); + } + + void animateBeatmaps() + { float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; - foreach (var item in loaded) + foreach (var item in beatmapContainer.Children) { item.MoveToY(yPos, 800, Easing.OutQuint); yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } - }); + } } private const int maximum_difficulty_icons = 18; From ded09b78cbbda03d95df2c75f17785a02b84c434 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 16:33:37 +0900 Subject: [PATCH 063/322] Avoid usage of AutoSize for DrawableCarouselItems in general --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 +-- .../Screens/Select/Carousel/DrawableCarouselItem.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9a822f83e3..a6cea5ee53 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -192,8 +192,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapContainer = new Container { X = 100, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, ChildrenEnumerable = visibleBeatmaps }; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 5bcaffa3cf..68825873ff 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.Select.Carousel protected DrawableCarouselItem() { RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; Alpha = 0; @@ -71,15 +70,13 @@ namespace osu.Game.Screens.Select.Carousel { MovementContainer = new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { Header = new CarouselHeader(), Content = new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, } } }, @@ -123,6 +120,10 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void ApplyState() { + // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. + // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. + Height = Item.TotalHeight; + Debug.Assert(Item != null); switch (Item.State.Value) From b536f571fd7885ef2048b871997d5a012fb2cedb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 16:35:06 +0900 Subject: [PATCH 064/322] Move header height propagation to update for safety --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 68825873ff..bafb338a04 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -89,10 +89,15 @@ namespace osu.Game.Screens.Select.Carousel { base.LoadComplete(); + UpdateItem(); + } + + protected override void Update() + { + base.Update(); + // avoid using fill flow for performance reasons. header size doesn't change after load. Content.Y = Header.Height; - - UpdateItem(); } protected virtual void UpdateItem() From 1f0aa974dd0a0155e0703036f3c2825e76c94e2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:09:54 +0900 Subject: [PATCH 065/322] Fix failing tests --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 +++++++++++++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 18 ++++----- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index d3ae3a19ea..8e4e2ac257 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -427,7 +427,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = createTestBeatmapSet(i, 3); + var set = createTestBeatmapSet(i); set.Beatmaps[0].StarDifficulty = 3 - i; set.Beatmaps[2].StarDifficulty = 6 + i; sets.Add(set); @@ -822,7 +822,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection is visible", selectedBeatmapVisible); } - private BeatmapSetInfo createTestBeatmapSet(int id, int minimumDifficulties = 1) + private BeatmapSetInfo createTestBeatmapSet(int id, bool randomDifficultyCount = false) { return new BeatmapSetInfo { @@ -836,7 +836,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"test set #{id}!", AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) }, - Beatmaps = getBeatmaps(RNG.Next(minimumDifficulties, 20)).ToList() + Beatmaps = getBeatmaps(randomDifficultyCount ? RNG.Next(1, 20) : 3).ToList() }; } @@ -846,14 +846,22 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < count; i++) { + float diff = (float)i / count * 10; + + string version = "Normal"; + if (diff > 6.6) + version = "Insane"; + else if (diff > 3.3) + version = "Hard"; + yield return new BeatmapInfo { OnlineBeatmapID = id++ * 10, - Version = "Normal", - StarDifficulty = RNG.NextSingle() * 10, + Version = version, + StarDifficulty = diff, BaseDifficulty = new BeatmapDifficulty { - OverallDifficulty = RNG.NextSingle() * 10, + OverallDifficulty = diff, } }; } @@ -899,7 +907,22 @@ namespace osu.Game.Tests.Visual.SongSelect { public bool PendingFilterTask => PendingFilter != null; - public IEnumerable Items => InternalChildren.OfType(); + public IEnumerable Items + { + get + { + foreach (var item in ScrollableContent) + { + yield return item; + + if (item is DrawableCarouselBeatmapSet set) + { + foreach (var difficulty in set.ChildItems) + yield return difficulty; + } + } + } + } protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty(); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index bcdbc53e26..b6ca6a242d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; - scrollableContent.Clear(false); + ScrollableContent.Clear(false); itemsCache.Invalidate(); scrollPositionCache.Invalidate(); @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Select private readonly Cached itemsCache = new Cached(); private readonly Cached scrollPositionCache = new Cached(); - private readonly Container scrollableContent; + protected readonly Container ScrollableContent; public Bindable RightClickScrollingEnabled = new Bindable(); @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { setPool, - scrollableContent = new Container + ScrollableContent = new Container { RelativeSizeAxes = Axes.X, } @@ -595,7 +595,7 @@ namespace osu.Game.Screens.Select var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); - foreach (var panel in scrollableContent.Children) + foreach (var panel in ScrollableContent.Children) { if (toDisplay.Remove(panel.Item)) { @@ -622,7 +622,7 @@ namespace osu.Game.Screens.Select panel.Depth = item.CarouselYPosition; panel.Y = item.CarouselYPosition; - scrollableContent.Add(panel); + ScrollableContent.Add(panel); } } @@ -630,7 +630,7 @@ namespace osu.Game.Screens.Select // This is common if a selected/collapsed state has changed. if (revalidateItems) { - foreach (DrawableCarouselItem panel in scrollableContent.Children) + foreach (DrawableCarouselItem panel in ScrollableContent.Children) { panel.MoveToY(panel.Item.CarouselYPosition, 800, Easing.OutQuint); } @@ -638,7 +638,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. - foreach (DrawableCarouselItem item in scrollableContent.Children) + foreach (DrawableCarouselItem item in ScrollableContent.Children) { updateItem(item); @@ -786,7 +786,7 @@ namespace osu.Game.Screens.Select } currentY += visibleHalfHeight; - scrollableContent.Height = currentY; + ScrollableContent.Height = currentY; if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) { @@ -841,7 +841,7 @@ namespace osu.Game.Screens.Select /// For nested items, the parent of the item to be updated. private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) { - Vector2 posInScroll = scrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); + Vector2 posInScroll = ScrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); From fdd4d95cdc6cdbb209b2dd99d2794dd45e428a4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:24:41 +0900 Subject: [PATCH 066/322] Fix difficulties being at incorrect vertical positions after filter is applied --- .../Carousel/DrawableCarouselBeatmapSet.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index a6cea5ee53..6c35ebfd97 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -171,29 +171,36 @@ namespace osu.Game.Screens.Select.Carousel MovementContainer.MoveToX(-100, 500, Easing.OutExpo); - if (beatmapContainer != null) + updateBeatmapDifficulties(); + } + + private void updateBeatmapDifficulties() + { + var carouselBeatmapSet = (CarouselBeatmapSet)Item; + + var visibleBeatmaps = carouselBeatmapSet.Children + .Where(c => c.Visible) + .ToArray(); + + // if we are already displaying all the correct beatmaps, only run animation updates. + // note that the displayed beatmaps may change due to the applied filter. + // a future optimisation could add/remove only changed difficulties rather than reinitialise. + if (beatmapContainer != null && visibleBeatmaps.Length == beatmapContainer.Count && visibleBeatmaps.All(b => beatmapContainer.Any(c => c.Item == b))) { - // if already loaded, we only need to re-animate. - animateBeatmaps(); + updateBeatmapYPositions(); } else { // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - var carouselBeatmapSet = (CarouselBeatmapSet)Item; - - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - var visibleBeatmaps = carouselBeatmapSet.Children - .Where(c => c.Visible) - .Select(c => c.CreateDrawableRepresentation()) - .ToArray(); beatmapContainer = new Container { X = 100, RelativeSizeAxes = Axes.Both, - ChildrenEnumerable = visibleBeatmaps + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()).ToArray() }; Logger.Log($"loading {visibleBeatmaps.Length} beatmaps for {Item}"); @@ -205,18 +212,18 @@ namespace osu.Game.Screens.Select.Carousel return; Content.Child = loaded; - animateBeatmaps(); + updateBeatmapYPositions(); }); } - void animateBeatmaps() + void updateBeatmapYPositions() { float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; - foreach (var item in beatmapContainer.Children) + foreach (var panel in beatmapContainer.Children) { - item.MoveToY(yPos, 800, Easing.OutQuint); - yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; + panel.MoveToY(yPos, 800, Easing.OutQuint); + yPos += panel.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } } } From 4160feb3da3dd6fc5c3154dd964ca2c8d61f92ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:26:17 +0900 Subject: [PATCH 067/322] Add test specifically for many panels visible --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 8e4e2ac257..239fa9ec4e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -41,6 +41,12 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestManyPanels() + { + loadBeatmaps(count: 5000, randomDifficulties: true); + } + [Test] public void TestKeyRepeat() { @@ -708,7 +714,7 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 15); } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null) + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, bool randomDifficulties = false) { bool changed = false; @@ -720,8 +726,8 @@ namespace osu.Game.Tests.Visual.SongSelect { beatmapSets = new List(); - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); + for (int i = 1; i <= (count ?? set_count); i++) + beatmapSets.Add(createTestBeatmapSet(i, randomDifficulties)); } carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); From f3b937e358a8489d3559e5972010caa193b4655a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:33:35 +0900 Subject: [PATCH 068/322] Fix masking issues with certain aspect ratio displays --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +++- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b6ca6a242d..bc2494e9b7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -845,7 +845,9 @@ namespace osu.Game.Screens.Select float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); - item.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); + // adjusting the item's overall X position can cause it to become masked away when + // child items (difficulties) are still visible. + item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index b06b60e6c5..a655186986 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - Header.MoveToX(-50, 500, Easing.OutExpo); + MovementContainer.MoveToX(-50, 500, Easing.OutExpo); background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); - Header.MoveToX(0, 500, Easing.OutExpo); + MovementContainer.MoveToX(0, 500, Easing.OutExpo); background.Colour = new Color4(20, 43, 51, 255); triangles.Colour = OsuColour.Gray(0.5f); From 9b2ebb8f0f07fef7f781edb9cf60a62a80206976 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:45:41 +0900 Subject: [PATCH 069/322] Fix main content DelayedLoadUnloadWrapper not getting a valid size before load --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 6c35ebfd97..b54325cf05 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -100,7 +100,8 @@ namespace osu.Game.Screens.Select.Carousel { Direction = FillDirection.Vertical, Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, - AutoSizeAxes = Axes.Both, + // required to ensure we load as soon as any part of the panel comes on screen + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new OsuSpriteText From d9a6a6b24543ddc7d1d7a259f37723700473376d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:55:56 +0900 Subject: [PATCH 070/322] Split content out into own class --- .../Carousel/DrawableCarouselBeatmapSet.cs | 66 +------------ ...nelBackground.cs => SetPanelBackground.cs} | 4 +- .../Select/Carousel/SetPanelContent.cs | 93 +++++++++++++++++++ 3 files changed, 98 insertions(+), 65 deletions(-) rename osu.Game/Screens/Select/Carousel/{PanelBackground.cs => SetPanelBackground.cs} (95%) create mode 100644 osu.Game/Screens/Select/Carousel/SetPanelContent.cs diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b54325cf05..ad5c4e5e4f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -10,16 +10,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osuTK; namespace osu.Game.Screens.Select.Carousel { @@ -85,7 +80,7 @@ namespace osu.Game.Screens.Select.Carousel new DelayedLoadUnloadWrapper(() => { Logger.Log($"loaded background item {beatmapSet}"); - var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + var background = new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, }; @@ -96,52 +91,8 @@ namespace osu.Game.Screens.Select.Carousel }, 300, 5000), new DelayedLoadUnloadWrapper(() => { - var mainFlow = new FillFlowContainer - { - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, - // required to ensure we load as soon as any part of the panel comes on screen - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), - Shadow = true, - }, - new OsuSpriteText - { - Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), - Shadow = true, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] - { - new BeatmapSetOnlineStatusPill - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5 }, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Status = beatmapSet.Status - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(3), - ChildrenEnumerable = getDifficultyIcons(), - }, - } - } - } - }; + // main content split into own class to reduce allocation before load operation triggers. + var mainFlow = new SetPanelContent((CarouselBeatmapSet)Item); mainFlow.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); @@ -229,17 +180,6 @@ namespace osu.Game.Screens.Select.Carousel } } - private const int maximum_difficulty_icons = 18; - - private IEnumerable getDifficultyIcons() - { - var beatmaps = ((CarouselBeatmapSet)Item).Beatmaps.ToList(); - - return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) - : beatmaps.Select(b => new FilterableDifficultyIcon(b)); - } - public MenuItem[] ContextMenuItems { get diff --git a/osu.Game/Screens/Select/Carousel/PanelBackground.cs b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs similarity index 95% rename from osu.Game/Screens/Select/Carousel/PanelBackground.cs rename to osu.Game/Screens/Select/Carousel/SetPanelBackground.cs index 587aa0a74e..6af0cbf4ab 100644 --- a/osu.Game/Screens/Select/Carousel/PanelBackground.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs @@ -9,9 +9,9 @@ using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public class PanelBackground : BufferedContainer + public class SetPanelBackground : BufferedContainer { - public PanelBackground(WorkingBeatmap working) + public SetPanelBackground(WorkingBeatmap working) { CacheDrawnFrameBuffer = true; RedrawOnScale = false; diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs new file mode 100644 index 0000000000..4e8d27f14d --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Select.Carousel +{ + public class SetPanelContent : CompositeDrawable + { + private readonly CarouselBeatmapSet carouselSet; + + public SetPanelContent(CarouselBeatmapSet carouselSet) + { + this.carouselSet = carouselSet; + + // required to ensure we load as soon as any part of the panel comes on screen + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + var beatmapSet = carouselSet.BeatmapSet; + + InternalChild = new FillFlowContainer + { + // required to ensure we load as soon as any part of the panel comes on screen + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + new OsuSpriteText + { + Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5 }, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapSet.Status + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), + ChildrenEnumerable = getDifficultyIcons(), + }, + } + } + } + }; + } + + private const int maximum_difficulty_icons = 18; + + private IEnumerable getDifficultyIcons() + { + var beatmaps = carouselSet.Beatmaps.ToList(); + + return beatmaps.Count > maximum_difficulty_icons + ? (IEnumerable)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) + : beatmaps.Select(b => new FilterableDifficultyIcon(b)); + } + } +} From b1ddb08a4efefd378d65ec8756e21c597b089030 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:57:38 +0900 Subject: [PATCH 071/322] Fix right click context menus appearing in incorrect locations --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index bafb338a04..a66135196a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Events; +using osuTK; namespace osu.Game.Screens.Select.Carousel { @@ -27,6 +28,9 @@ namespace osu.Game.Screens.Select.Carousel protected readonly Container MovementContainer; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + Header.ReceivePositionalInputAt(screenSpacePos); + private CarouselItem item; public CarouselItem Item From 69650c16fc539d66a589054792a1b5524d92a842 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:13:36 +0900 Subject: [PATCH 072/322] Simplify vertical position calculations by including spacing in height definition --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 9 +++++++-- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index bc2494e9b7..955693a67c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -775,7 +775,7 @@ namespace osu.Game.Screens.Select break; } - scrollTarget += b.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; + scrollTarget += b.TotalHeight; } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index f2f8cb9bd9..7935debac7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * (DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING + DrawableCarouselBeatmap.HEIGHT); + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; default: return DrawableCarouselBeatmapSet.HEIGHT; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a655186986..49a370724e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -33,7 +33,12 @@ namespace osu.Game.Screens.Select.Carousel { public const float CAROUSEL_BEATMAP_SPACING = 5; - public const float HEIGHT = MAX_HEIGHT * 0.6f; // TODO: add once base class is fixed + CAROUSEL_BEATMAP_SPACING; + /// + /// The height of a carousel beatmap, including vertical spacing. + /// + public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING; + + private const float height = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmap; @@ -70,7 +75,7 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { - Header.Height = HEIGHT; + Header.Height = height; if (songSelect != null) { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index ad5c4e5e4f..68fea14757 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Select.Carousel foreach (var panel in beatmapContainer.Children) { panel.MoveToY(yPos, 800, Easing.OutQuint); - yPos += panel.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; + yPos += panel.Item.TotalHeight; } } } From 3d9ea852ecf48cf40f8669665815b8c2bfb18abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:14:23 +0900 Subject: [PATCH 073/322] Remove masking override (no longer needed as our size is now correct) --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 68fea14757..b80e789429 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -22,9 +21,6 @@ namespace osu.Game.Screens.Select.Carousel { public const float HEIGHT = MAX_HEIGHT; - // TODO: don't do this. need to split out the base class' style so our height isn't fixed to the panel display height (and autosize?). - protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; - private Action restoreHiddenRequested; private Action viewDetails; From 83358d487fb5770877948e671621eada6cc91abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:18:22 +0900 Subject: [PATCH 074/322] Remove logging --- osu.Game/Screens/Select/BeatmapCarousel.cs | 20 ++++++++----------- .../Carousel/DrawableCarouselBeatmapSet.cs | 5 ----- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 955693a67c..e53beaef0b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1,30 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; -using System.Linq; -using osu.Game.Configuration; -using osuTK.Input; -using osu.Framework.Utils; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; -using osu.Framework.Threading; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; +using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; +using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Select { @@ -589,8 +588,6 @@ namespace osu.Game.Screens.Select // This involves fetching new items from the pool, returning no-longer required items. if (revalidateItems || newDisplayRange != displayedRange) { - Logger.Log("revalidation requested"); - displayedRange = newDisplayRange; var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); @@ -616,7 +613,6 @@ namespace osu.Game.Screens.Select // Add those items within the previously found index range that should be displayed. foreach (var item in toDisplay) { - Logger.Log($"getting panel for {item} from pool"); var panel = setPool.Get(p => p.Item = item); panel.Depth = item.CarouselYPosition; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b80e789429..bf76adc6ac 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics.UserInterface; @@ -70,12 +69,10 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; - Logger.Log($"updating item {beatmapSet}"); Header.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { - Logger.Log($"loaded background item {beatmapSet}"); var background = new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, @@ -151,8 +148,6 @@ namespace osu.Game.Screens.Select.Carousel ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()).ToArray() }; - Logger.Log($"loading {visibleBeatmaps.Length} beatmaps for {Item}"); - LoadComponentAsync(beatmapContainer, loaded => { // make sure the pooled target hasn't changed. From 4f4f2225143c425e9ec9f35592adddffa16284f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:28:28 +0900 Subject: [PATCH 075/322] Remove unnecessary fade (already applied by base DrawableCarouselItem) --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index bf76adc6ac..aae4d0df5d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -103,10 +103,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapContainer != null) { foreach (var beatmap in beatmapContainer) - { beatmap.MoveToY(0, 800, Easing.OutQuint); - beatmap.FadeOut(80); - } } } From 40a0ab7aaaf1139fbcf068ad0e7d358772c43118 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:33:31 +0900 Subject: [PATCH 076/322] Avoid allocating CarouselItems for bounds checks --- osu.Game/Screens/Select/BeatmapCarousel.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e53beaef0b..340f30c120 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -646,19 +646,24 @@ namespace osu.Game.Screens.Select } } + private readonly CarouselBoundsItem carouselBoundsItem = new CarouselBoundsItem(); + private (int firstIndex, int lastIndex) getDisplayRange() { // Find index range of all items that should be on-screen - // TODO: reduce allocs of CarouselBoundsItem. - int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound - distance_offscreen_to_preload)); + carouselBoundsItem.CarouselYPosition = visibleUpperBound - distance_offscreen_to_preload; + int firstIndex = visibleItems.BinarySearch(carouselBoundsItem); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound + distance_offscreen_to_preload)); + + carouselBoundsItem.CarouselYPosition = visibleBottomBound + distance_offscreen_to_preload; + int lastIndex = visibleItems.BinarySearch(carouselBoundsItem); if (lastIndex < 0) lastIndex = ~lastIndex; // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); lastIndex = Math.Min(visibleItems.Count, lastIndex + 1); + return (firstIndex, lastIndex); } @@ -856,11 +861,6 @@ namespace osu.Game.Screens.Select /// private class CarouselBoundsItem : CarouselItem { - public CarouselBoundsItem(in float pos) - { - CarouselYPosition = pos; - } - public override DrawableCarouselItem CreateDrawableRepresentation() => throw new NotImplementedException(); } From a1801f8ae40f7e65da4305f7ab08dbb91ce0f7f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:33:55 +0900 Subject: [PATCH 077/322] Unmark todo for now --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 340f30c120..dcebabae4d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -604,7 +604,7 @@ namespace osu.Game.Screens.Select // remove but only if too far off-screen if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) { - // todo: may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). panel.ClearTransforms(); panel.Expire(); } From 2346644c048b781fa6eae9fb144d038e9ed1ee40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:47:35 +0900 Subject: [PATCH 078/322] Switch DelayedLoadUnloadWrappers to DelayedLoadWrappers Due to pooling usage, there is no time we need to unload. Switching to DelayedLoadWrapper cleans up the code and reduces overhead substantially. --- .../Carousel/DrawableCarouselBeatmapSet.cs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index aae4d0df5d..42d073976e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -69,29 +69,25 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; + DelayedLoadWrapper background; + DelayedLoadWrapper mainFlow; + Header.Children = new Drawable[] { - new DelayedLoadUnloadWrapper(() => + background = new DelayedLoadWrapper(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { - var background = new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) - { - RelativeSizeAxes = Axes.Both, - }; - - background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); - - return background; - }, 300, 5000), - new DelayedLoadUnloadWrapper(() => - { - // main content split into own class to reduce allocation before load operation triggers. - var mainFlow = new SetPanelContent((CarouselBeatmapSet)Item); - - mainFlow.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); - - return mainFlow; - }, 100, 5000) + RelativeSizeAxes = Axes.Both, + }, 300), + mainFlow = new DelayedLoadWrapper(new SetPanelContent((CarouselBeatmapSet)Item), 100), }; + + background.DelayedLoadComplete += fadeContentIn; + mainFlow.DelayedLoadComplete += fadeContentIn; + } + + private void fadeContentIn(Drawable d) + { + d.FadeInFromZero(1000, Easing.OutQuint); } protected override void Deselected() From 834b0186f41f64b12a23f70abfdb34bc923d5074 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:50:10 +0900 Subject: [PATCH 079/322] Adjust fade duration to be slightly shorter --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 42d073976e..e83c460f15 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -85,10 +85,7 @@ namespace osu.Game.Screens.Select.Carousel mainFlow.DelayedLoadComplete += fadeContentIn; } - private void fadeContentIn(Drawable d) - { - d.FadeInFromZero(1000, Easing.OutQuint); - } + private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); protected override void Deselected() { From 8eca28e8bc3986ed99c78f54fbe6769dca3af6cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:10:35 +0900 Subject: [PATCH 080/322] Add comment about off-screen loading --- osu.Game/Screens/Select/BeatmapCarousel.cs | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index dcebabae4d..21777c5c7c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -74,6 +74,18 @@ namespace osu.Game.Screens.Select public override bool PropagatePositionalInputSubTree => AllowSelection; public override bool PropagateNonPositionalInputSubTree => AllowSelection; + private (int first, int last) displayedRange; + + /// + /// Extend the range to retain already loaded pooled drawables. + /// + private const float distance_offscreen_before_unload = 1024; + + /// + /// Extend the range to update positions / retrieve pooled drawables outside of visible range. + /// + private const float distance_offscreen_to_preload = 512; // todo: adjust this appropriately once we can make set panel contents load while off-screen. + /// /// Whether carousel items have completed asynchronously loaded. /// @@ -557,18 +569,6 @@ namespace osu.Game.Screens.Select #endregion - private (int first, int last) displayedRange; - - /// - /// Extend the range to retain already loaded pooled drawables. - /// - private const float distance_offscreen_before_unload = 1024; - - /// - /// Extend the range to update positions / retrieve pooled drawables outside of visible range. - /// - private const float distance_offscreen_to_preload = 256; - protected override void Update() { base.Update(); From 37daefc2b59da55abe3fcc9a6fc62cf7deeee623 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:12:33 +0900 Subject: [PATCH 081/322] Remove outdated comment --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index a66135196a..df8d5cd565 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -99,8 +99,6 @@ namespace osu.Game.Screens.Select.Carousel protected override void Update() { base.Update(); - - // avoid using fill flow for performance reasons. header size doesn't change after load. Content.Y = Header.Height; } From 5d11db7753212439c219b7c60863eb4bfb6680cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:15:56 +0900 Subject: [PATCH 082/322] Locallise ChildItems to DrawableCarouselBeatmapSet for clarity --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 4 ---- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 239fa9ec4e..0ba8cfeb28 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -923,7 +923,7 @@ namespace osu.Game.Tests.Visual.SongSelect if (item is DrawableCarouselBeatmapSet set) { - foreach (var difficulty in set.ChildItems) + foreach (var difficulty in set.DrawableBeatmaps) yield return difficulty; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 21777c5c7c..c011ea7e05 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -640,7 +640,7 @@ namespace osu.Game.Screens.Select if (item is DrawableCarouselBeatmapSet set) { - foreach (var diff in set.ChildItems) + foreach (var diff in set.DrawableBeatmaps) updateItem(diff, item); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e83c460f15..79387a9905 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - public override IEnumerable ChildItems => beatmapContainer?.Children ?? base.ChildItems; + public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty(); private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index df8d5cd565..cde3edad39 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -1,9 +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.Collections.Generic; using System.Diagnostics; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -62,8 +60,6 @@ namespace osu.Game.Screens.Select.Carousel } } - public virtual IEnumerable ChildItems => Enumerable.Empty(); - protected DrawableCarouselItem() { RelativeSizeAxes = Axes.X; From 75b6a5e17e23377b33f5ea463ecff07e40ac5b56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:17:23 +0900 Subject: [PATCH 083/322] Remove unnecessary hack (fixed via framework update) --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 79387a9905..cd1c0a08c7 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -113,9 +113,7 @@ namespace osu.Game.Screens.Select.Carousel { var carouselBeatmapSet = (CarouselBeatmapSet)Item; - var visibleBeatmaps = carouselBeatmapSet.Children - .Where(c => c.Visible) - .ToArray(); + var visibleBeatmaps = carouselBeatmapSet.Children.Where(c => c.Visible).ToArray(); // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. @@ -129,13 +127,11 @@ namespace osu.Game.Screens.Select.Carousel // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - beatmapContainer = new Container { X = 100, RelativeSizeAxes = Axes.Both, - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()).ToArray() + ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()) }; LoadComponentAsync(beatmapContainer, loaded => From 3d416f4d6475c815424e71a8e97a34cb6b933b0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:20:46 +0900 Subject: [PATCH 084/322] Clean up beatmapSet resolution in DrawableCarouselBeatmapSet --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index cd1c0a08c7..93dc79242e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -34,10 +35,10 @@ namespace osu.Game.Screens.Select.Carousel public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty(); - private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; - private Container beatmapContainer; + private BeatmapSetInfo beatmapSet; + [Resolved] private BeatmapManager manager { get; set; } @@ -69,6 +70,8 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; + beatmapSet = ((CarouselBeatmapSet)Item).BeatmapSet; + DelayedLoadWrapper background; DelayedLoadWrapper mainFlow; @@ -161,6 +164,8 @@ namespace osu.Game.Screens.Select.Carousel { get { + Debug.Assert(beatmapSet != null); + List items = new List(); if (Item.State.Value == CarouselItemState.NotSelected) @@ -189,6 +194,8 @@ namespace osu.Game.Screens.Select.Carousel private MenuItem createCollectionMenuItem(BeatmapCollection collection) { + Debug.Assert(beatmapSet != null); + TernaryState state; var countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b)); From 8057ea1097b8d3fe570a4183d40e3369ecb82ee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 20:50:36 +0900 Subject: [PATCH 085/322] Fix formatting issues --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 10 ++++++++-- .../Select/Carousel/FilterableDifficultyIcon.cs | 2 +- .../Select/Carousel/FilterableGroupedDifficultyIcon.cs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index f59cccd8b6..f1120f55a6 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -77,7 +77,10 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.BorderThickness = 0; BorderContainer.EdgeEffect = new EdgeEffectParameters { - Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, Colour = Color4.Black.Opacity(100), + Type = EdgeEffectType.Shadow, + Offset = new Vector2(1), + Radius = 10, + Colour = Color4.Black.Opacity(100), }; break; @@ -85,7 +88,10 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.BorderThickness = 2.5f; BorderContainer.EdgeEffect = new EdgeEffectParameters { - Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), Radius = 20, Roundness = 10, + Type = EdgeEffectType.Glow, + Colour = new Color4(130, 204, 255, 150), + Radius = 20, + Roundness = 10, }; break; } diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index 591e9fea22..aa832623fe 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -29,4 +29,4 @@ namespace osu.Game.Screens.Select.Carousel return true; } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs index 73b5781a37..31a651d2c8 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs @@ -35,4 +35,4 @@ namespace osu.Game.Screens.Select.Carousel this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100); } } -} \ No newline at end of file +} From e662dc5342b26ecedd265b55c121a058006e2ad3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 20:57:26 +0900 Subject: [PATCH 086/322] Add missing licence headers --- osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs | 3 +++ .../Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs | 3 +++ osu.Game/Screens/Select/Carousel/SetPanelBackground.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index aa832623fe..dce593b85c 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs index 31a651d2c8..d2f9ed3a6a 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs @@ -1,3 +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.Graphics; diff --git a/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs index 6af0cbf4ab..25139b27db 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; From 07e6609e6de38054cbc5e9c64e04f8317da75e8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 14:15:53 +0900 Subject: [PATCH 087/322] Disable difficulty calculation for set-level difficulty icons --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 15 ++++++++++++--- .../Select/Carousel/FilterableDifficultyIcon.cs | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 45327d4514..a1d5e33d1e 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -47,7 +47,10 @@ namespace osu.Game.Beatmaps.Drawables private readonly IReadOnlyList mods; private readonly bool shouldShowTooltip; - private readonly IBindable difficultyBindable = new Bindable(); + + private readonly bool performBackgroundDifficultyLookup; + + private readonly Bindable difficultyBindable = new Bindable(); private Drawable background; @@ -70,10 +73,12 @@ namespace osu.Game.Beatmaps.Drawables /// /// The beatmap to show the difficulty of. /// Whether to display a tooltip when hovered. - public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true) + /// Whether to perform difficulty lookup (including calculation if necessary). + public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) { this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); this.shouldShowTooltip = shouldShowTooltip; + this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup; AutoSizeAxes = Axes.Both; @@ -112,9 +117,13 @@ namespace osu.Game.Beatmaps.Drawables // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } }, - new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0), }; + if (performBackgroundDifficultyLookup) + iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); + else + difficultyBindable.Value = new StarDifficulty(beatmap.StarDifficulty, 0); + difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating)); } diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index dce593b85c..51fe7796c7 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselBeatmap Item; public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.Beatmap) + : base(item.Beatmap, performBackgroundDifficultyLookup: false) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); From 30e1fce7a4e82c7b595eb25115c9b0c38f0b7da7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 15:10:50 +0900 Subject: [PATCH 088/322] Reduce alloc overhead of DrawableCarouselBeatmapSet using new function-based ctor --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 93dc79242e..703b91c517 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -77,11 +77,11 @@ namespace osu.Game.Screens.Select.Carousel Header.Children = new Drawable[] { - background = new DelayedLoadWrapper(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, }, 300), - mainFlow = new DelayedLoadWrapper(new SetPanelContent((CarouselBeatmapSet)Item), 100), + mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100), }; background.DelayedLoadComplete += fadeContentIn; From 4590d9b93b874550fde5a921bedff9353d48b69a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 13:04:12 +0900 Subject: [PATCH 089/322] Remove outdated comment logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c011ea7e05..83e20909a1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -762,7 +762,6 @@ namespace osu.Game.Screens.Select // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas) // then reapply the top semi-transparent area (because carousel's screen space starts below it) - // and finally add half of the panel's own height to achieve vertical centering of the panel itself scrollTarget = currentY + DrawableCarouselBeatmapSet.HEIGHT - visibleHalfHeight + BleedTop; foreach (var b in set.Beatmaps) From ee0efa0b4c1a5e19b665e3bf89999714d24408c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 13:05:42 +0900 Subject: [PATCH 090/322] Fix off-by-one in display range retrieval logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 83e20909a1..04eff79533 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -590,7 +590,7 @@ namespace osu.Game.Screens.Select { displayedRange = newDisplayRange; - var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); + var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); foreach (var panel in ScrollableContent.Children) { From bff3856c83ec9459185734fffeeef1aee3a41a29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 13:13:32 +0900 Subject: [PATCH 091/322] Account for panel height when removing as off-screen --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 04eff79533..06327bac3f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -602,7 +602,7 @@ namespace osu.Game.Screens.Select // panel loaded as drawable but not required by visible range. // remove but only if too far off-screen - if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) { // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). panel.ClearTransforms(); From 044622a7a60f3e588b2d2b6543ab6e81547bedc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 18:41:17 +0900 Subject: [PATCH 092/322] Fix out of bounds issues --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 06327bac3f..54860a894b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -662,7 +662,7 @@ namespace osu.Game.Screens.Select // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Min(visibleItems.Count, lastIndex + 1); + lastIndex = Math.Clamp(lastIndex + 1, firstIndex, visibleItems.Count - 1); return (firstIndex, lastIndex); } From 1c2185e9691c1a595145d123555ad7decd5e2ffb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 18:41:28 +0900 Subject: [PATCH 093/322] Replace comment with link to issue --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 54860a894b..e3cd3461c2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -895,7 +895,7 @@ namespace osu.Game.Screens.Select /// public bool UserScrolling { get; private set; } - // ReSharper disable once OptionalParameterHierarchyMismatch fuck off rider + // ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910) protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { UserScrolling = true; From 9106e97c37697812b3af9f508724273c27d4f457 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 19:10:01 +0900 Subject: [PATCH 094/322] Ensure max value in clamp is at least zero --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e3cd3461c2..92abec0eee 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -662,7 +662,7 @@ namespace osu.Game.Screens.Select // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Clamp(lastIndex + 1, firstIndex, visibleItems.Count - 1); + lastIndex = Math.Clamp(lastIndex + 1, firstIndex, Math.Max(0, visibleItems.Count - 1)); return (firstIndex, lastIndex); } From d5940193a2dce5dd5e35568b0784c624f1117500 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 19:55:20 +0900 Subject: [PATCH 095/322] Ensure visible items is greater than zero before trying to display a range --- osu.Game/Screens/Select/BeatmapCarousel.cs | 49 ++++++++++++---------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 92abec0eee..83631fd383 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -590,36 +590,39 @@ namespace osu.Game.Screens.Select { displayedRange = newDisplayRange; - var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); - - foreach (var panel in ScrollableContent.Children) + if (visibleItems.Count > 0) { - if (toDisplay.Remove(panel.Item)) + var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); + + foreach (var panel in ScrollableContent.Children) { - // panel already displayed. - continue; + if (toDisplay.Remove(panel.Item)) + { + // panel already displayed. + continue; + } + + // panel loaded as drawable but not required by visible range. + // remove but only if too far off-screen + if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + { + // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + panel.ClearTransforms(); + panel.Expire(); + } } - // panel loaded as drawable but not required by visible range. - // remove but only if too far off-screen - if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + // Add those items within the previously found index range that should be displayed. + foreach (var item in toDisplay) { - // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). - panel.ClearTransforms(); - panel.Expire(); + var panel = setPool.Get(p => p.Item = item); + + panel.Depth = item.CarouselYPosition; + panel.Y = item.CarouselYPosition; + + ScrollableContent.Add(panel); } } - - // Add those items within the previously found index range that should be displayed. - foreach (var item in toDisplay) - { - var panel = setPool.Get(p => p.Item = item); - - panel.Depth = item.CarouselYPosition; - panel.Y = item.CarouselYPosition; - - ScrollableContent.Add(panel); - } } // Finally, if the filtered items have changed, animate drawables to their new locations. From 053c7a69a64ee67243f6a10d81c92644a824febb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 20:22:48 +0200 Subject: [PATCH 096/322] Fix code style issues & compilation failures --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 ++-- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 5 ++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index a7ccc62e40..1d5d2f9431 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,6 +20,7 @@ using osu.Game.Skinning; using osu.Framework.Allocation; using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Screens; using osuTK; namespace osu.Game.Rulesets.Osu.UI @@ -36,12 +37,10 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private Bindable showPlayfieldBorder; private readonly IDictionary> poolDictionary = new Dictionary>(); - public OsuPlayfield() { InternalChildren = new Drawable[] @@ -87,6 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI private void load(OsuConfigManager config) { showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); + if (showPlayfieldBorder.Value) { AddInternal(new PlayfieldBorder diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 755f4e990c..e8a07fc831 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -76,14 +76,13 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsEnumDropdown { LabelText = "Score display mode", - Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode), + Current = config.GetBindable(OsuSetting.ScoreDisplayMode), Keywords = new[] { "scoring" } }, new SettingsCheckbox { LabelText = "Show playfield border", - Bindable = config.GetBindable(OsuSetting.ShowPlayfieldBorder) - Current = config.GetBindable(OsuSetting.ScoreDisplayMode), + Current = config.GetBindable(OsuSetting.ShowPlayfieldBorder), }, }; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fc80d4f3df..f4457c6aeb 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] { LayerBelowRuleset, - new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } + new PlayfieldBorder { RelativeSizeAxes = Axes.Both } }), drawableRulesetWrapper, // layers above playfield From 4af3fd1ed68830f250704246e3957bcfb36e92ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 20:41:45 +0200 Subject: [PATCH 097/322] Allow toggling border on & off during gameplay --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 18 ++++++++++-------- osu.Game/Screens/PlayfieldBorder.cs | 7 ++++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 1d5d2f9431..8904345825 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuPlayfield : Playfield { + private readonly PlayfieldBorder playfieldBorder; private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; @@ -45,6 +46,11 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { + playfieldBorder = new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + Depth = 3 + }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both @@ -86,16 +92,12 @@ namespace osu.Game.Rulesets.Osu.UI private void load(OsuConfigManager config) { showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); - - if (showPlayfieldBorder.Value) - { - AddInternal(new PlayfieldBorder - { - RelativeSizeAxes = Axes.Both - }); - } + showPlayfieldBorder.BindValueChanged(updateBorderVisibility, true); } + private void updateBorderVisibility(ValueChangedEvent settingChange) + => playfieldBorder.State.Value = settingChange.NewValue ? Visibility.Visible : Visibility.Hidden; + public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; diff --git a/osu.Game/Screens/PlayfieldBorder.cs b/osu.Game/Screens/PlayfieldBorder.cs index a3be38f0a2..e88b73bc71 100644 --- a/osu.Game/Screens/PlayfieldBorder.cs +++ b/osu.Game/Screens/PlayfieldBorder.cs @@ -11,8 +11,10 @@ namespace osu.Game.Screens /// /// Provides a border around the playfield. /// - public class PlayfieldBorder : CompositeDrawable + public class PlayfieldBorder : VisibilityContainer { + private const int fade_duration = 200; + public PlayfieldBorder() { RelativeSizeAxes = Axes.Both; @@ -28,5 +30,8 @@ namespace osu.Game.Screens AlwaysPresent = true }; } + + protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); } } From 4267d23d598f2635df363b5b42019c91ddc72ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 20:56:34 +0200 Subject: [PATCH 098/322] Move border to more appropriate namespace --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 - osu.Game/{Screens => Rulesets/UI}/PlayfieldBorder.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) rename osu.Game/{Screens => Rulesets/UI}/PlayfieldBorder.cs (97%) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 8904345825..e536bd1503 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,6 @@ using osu.Game.Skinning; using osu.Framework.Allocation; using osu.Game.Configuration; using osu.Framework.Bindables; -using osu.Game.Screens; using osuTK; namespace osu.Game.Rulesets.Osu.UI diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f4457c6aeb..4c552c5083 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Screens; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Components.TernaryButtons; diff --git a/osu.Game/Screens/PlayfieldBorder.cs b/osu.Game/Rulesets/UI/PlayfieldBorder.cs similarity index 97% rename from osu.Game/Screens/PlayfieldBorder.cs rename to osu.Game/Rulesets/UI/PlayfieldBorder.cs index e88b73bc71..66371c89ad 100644 --- a/osu.Game/Screens/PlayfieldBorder.cs +++ b/osu.Game/Rulesets/UI/PlayfieldBorder.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace osu.Game.Screens +namespace osu.Game.Rulesets.UI { /// /// Provides a border around the playfield. From dbda18acea2f9644bfc5a618c52ecb08de85a55f Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 19 Oct 2020 12:04:23 -0700 Subject: [PATCH 099/322] Fix autoplay/replay settings going off screen on some legacy skins --- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index c3de249bf8..af5e2b80e1 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -208,17 +208,9 @@ namespace osu.Game.Screens.Play { base.Update(); - float topRightOffset = 0; + // for now align with the accuracy counter. eventually this will be user customisable. + topRightElements.Y = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; - // fetch the bottom-most position of any main ui element that is anchored to the top of the screen. - // consider this kind of temporary. - foreach (var d in mainUIElements) - { - if (d is SkinnableDrawable sd && (sd.Drawable.Anchor & Anchor.y0) > 0) - topRightOffset = Math.Max(sd.Drawable.ScreenSpaceDrawQuad.BottomRight.Y, topRightOffset); - } - - topRightElements.Y = ToLocalSpace(new Vector2(0, topRightOffset)).Y; bottomRightElements.Y = -Progress.Height; } From bca05397350e213213aabce18d4c233187809cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 21:00:49 +0200 Subject: [PATCH 100/322] Move setting to osu! ruleset subsection --- .../Configuration/OsuRulesetConfigManager.cs | 4 +++- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 10 +++++----- osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs | 5 +++++ osu.Game/Configuration/OsuConfigManager.cs | 3 --- .../Settings/Sections/Gameplay/GeneralSettings.cs | 5 ----- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index f76635a932..da8767c017 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Configuration Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); Set(OsuRulesetSetting.ShowCursorTrail, true); + Set(OsuRulesetSetting.ShowPlayfieldBorder, false); } } @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Configuration { SnakingInSliders, SnakingOutSliders, - ShowCursorTrail + ShowCursorTrail, + ShowPlayfieldBorder, } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index e536bd1503..26fe686b0d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -18,8 +18,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osu.Framework.Allocation; -using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Rulesets.Osu.Configuration; using osuTK; namespace osu.Game.Rulesets.Osu.UI @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private Bindable showPlayfieldBorder; + private readonly Bindable showPlayfieldBorder = new BindableBool(); private readonly IDictionary> poolDictionary = new Dictionary>(); @@ -87,10 +87,10 @@ namespace osu.Game.Rulesets.Osu.UI AddRangeInternal(poolDictionary.Values); } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + [BackgroundDependencyLoader(true)] + private void load(OsuRulesetConfigManager config) { - showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); + config?.BindWith(OsuRulesetSetting.ShowPlayfieldBorder, showPlayfieldBorder); showPlayfieldBorder.BindValueChanged(updateBorderVisibility, true); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 3870f303b4..28c609f412 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -39,6 +39,11 @@ namespace osu.Game.Rulesets.Osu.UI LabelText = "Cursor trail", Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, + new SettingsCheckbox + { + LabelText = "Show playfield border", + Current = config.GetBindable(OsuRulesetSetting.ShowPlayfieldBorder), + }, }; } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 14be15bb94..78179a781a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -106,8 +106,6 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); Set(OsuSetting.GameplayDisableWinKey, true); - Set(OsuSetting.ShowPlayfieldBorder, false); - // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -241,6 +239,5 @@ namespace osu.Game.Configuration HitLighting, MenuBackgroundSource, GameplayDisableWinKey, - ShowPlayfieldBorder, } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index e8a07fc831..07ad3c6b64 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -79,11 +79,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Current = config.GetBindable(OsuSetting.ScoreDisplayMode), Keywords = new[] { "scoring" } }, - new SettingsCheckbox - { - LabelText = "Show playfield border", - Current = config.GetBindable(OsuSetting.ShowPlayfieldBorder), - }, }; if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) From 7c388f1132cad566f0b8ef05f8326fd1a383899c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 21:20:13 +0200 Subject: [PATCH 101/322] Move editor playfield border locally to osu! composer --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 +++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 912a705d16..d9de9d2c5d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -56,6 +56,11 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { + LayerBelowRuleset.Add(new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + }); LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4c552c5083..c9dd061b48 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -100,11 +100,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { // layers below playfield - drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] - { - LayerBelowRuleset, - new PlayfieldBorder { RelativeSizeAxes = Axes.Both } - }), + drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(LayerBelowRuleset), drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() From fef6e55b397130d90b055b2939088cddb346d3b8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 19 Oct 2020 12:32:16 -0700 Subject: [PATCH 102/322] Remove unused using and field --- 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 af5e2b80e1..0a45df51a0 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; -using osu.Game.Skinning; using osuTK; using osuTK.Input; @@ -66,8 +65,6 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; - private readonly Container mainUIElements; - private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -92,7 +89,7 @@ namespace osu.Game.Screens.Play { new Drawable[] { - mainUIElements = new Container + new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 6e4b28ed1e69cdb3aaa574b035ce0dd040d82933 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Oct 2020 00:32:44 +0300 Subject: [PATCH 103/322] Different version of epilepsy warning display --- .../Screens/Play/BeatmapMetadataDisplay.cs | 44 ------- osu.Game/Screens/Play/PlayerLoader.cs | 114 +++++++++++++++++- 2 files changed, 113 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index bab141a75e..5530b4beac 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -49,44 +49,6 @@ namespace osu.Game.Screens.Play } } - private class EpilepsyWarning : FillFlowContainer - { - public EpilepsyWarning(bool warning) - { - if (warning) - this.Show(); - else - this.Hide(); - - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Vertical; - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Icon = FontAwesome.Solid.ExclamationTriangle, - Size = new Vector2(40), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "This beatmap contains scenes with rapidly flashing colours.", - Font = OsuFont.GetFont(size: 20), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Please take caution if you are affected by epilepsy.", - Font = OsuFont.GetFont(size: 20), - } - }; - } - } - private readonly WorkingBeatmap beatmap; private readonly Bindable> mods; private readonly Drawable facade; @@ -201,12 +163,6 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 }, Current = mods }, - new EpilepsyWarning(beatmap.BeatmapInfo.EpilepsyWarning) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 20 }, - } }, } }; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 93a734589c..4c04627651 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -6,16 +6,21 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -84,6 +89,8 @@ namespace osu.Game.Screens.Play private bool hideOverlays; + private bool epilepsyShown; + private InputManager inputManager; private IdleTracker idleTracker; @@ -308,7 +315,27 @@ namespace osu.Game.Screens.Play { contentOut(); - this.Delay(250).Schedule(() => + if (true && !epilepsyShown) + { + EpilepsyWarning warning; + + AddInternal(warning = new EpilepsyWarning + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { Value = Visibility.Visible } + }); + + epilepsyShown = true; + + this.Delay(2000).Schedule(() => + { + warning.Hide(); + warning.Expire(); + }); + } + + this.Delay(epilepsyShown ? 2500 : 250).Schedule(() => { if (!this.IsCurrentScreen()) return; @@ -398,5 +425,90 @@ namespace osu.Game.Screens.Play } #endregion + + private class EpilepsyWarning : VisibilityContainer + { + private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); + + private Track track; + private FillFlowContainer warningContent; + + public EpilepsyWarning() + { + RelativeSizeAxes = Axes.Both; + Alpha = 0f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, IBindable beatmap) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + warningContent = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SpriteIcon + { + Colour = colours.Yellow, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(50), + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.Centre, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }.With(tfc => + { + tfc.AddText("This beatmap contains scenes with "); + tfc.AddText("rapidly flashing colours", s => + { + s.Font = s.Font.With(weight: FontWeight.Bold); + s.Colour = colours.Yellow; + }); + tfc.AddText("."); + + tfc.NewParagraph(); + tfc.AddText("Please take caution if you are affected by epilepsy."); + }), + } + } + }; + + track = beatmap.Value.Track; + track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + + protected override void PopIn() + { + this.FadeIn(500, Easing.InQuint) + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + + warningContent.FadeIn(500, Easing.InQuint); + } + + protected override void PopOut() => this.FadeOut(500, Easing.InQuint); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + } } } From afa86f959f184acb76ace6b5b7ae68ff490b8440 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 19 Oct 2020 23:38:06 +0200 Subject: [PATCH 104/322] Changed scales of Seeding and Win screen to match the original These were measured by pixel-to-pixel comparing master vs this branch in ShareX at the same resolution. --- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 3 +-- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index b343608e69..32830713f6 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -288,8 +288,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro AutoSizeAxes = Axes.Both; Flag.RelativeSizeAxes = Axes.None; - Flag.Size = new Vector2(300, 200); - Flag.Scale = new Vector2(0.3f); + Flag.Scale = new Vector2(1.4f); InternalChild = new FillFlowContainer { diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index dde140ab91..3972c590ea 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -93,6 +93,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(-300, 10), + Scale = new Vector2(2.2f) }, new FillFlowContainer { From 44279ed34741fc9a376735d45620bb371b44e79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:46:09 +0200 Subject: [PATCH 105/322] Remove unused using directive --- osu.Game/Screens/Play/PlayerLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 4c04627651..76020f5385 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -20,7 +20,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; From aeca61eb3ecf652ba1dab3ec0abc3bc935f535ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:48:02 +0200 Subject: [PATCH 106/322] Split warning to separate file --- osu.Game/Screens/Play/EpilepsyWarning.cs | 105 +++++++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 89 ------------------- 2 files changed, 105 insertions(+), 89 deletions(-) create mode 100644 osu.Game/Screens/Play/EpilepsyWarning.cs diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs new file mode 100644 index 0000000000..e115c7e057 --- /dev/null +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -0,0 +1,105 @@ +// 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.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class EpilepsyWarning : VisibilityContainer + { + private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); + + private Track track; + private FillFlowContainer warningContent; + + public EpilepsyWarning() + { + RelativeSizeAxes = Axes.Both; + Alpha = 0f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, IBindable beatmap) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + warningContent = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SpriteIcon + { + Colour = colours.Yellow, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(50), + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.Centre, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }.With(tfc => + { + tfc.AddText("This beatmap contains scenes with "); + tfc.AddText("rapidly flashing colours", s => + { + s.Font = s.Font.With(weight: FontWeight.Bold); + s.Colour = colours.Yellow; + }); + tfc.AddText("."); + + tfc.NewParagraph(); + tfc.AddText("Please take caution if you are affected by epilepsy."); + }), + } + } + }; + + track = beatmap.Value.Track; + track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + + protected override void PopIn() + { + this.FadeIn(500, Easing.InQuint) + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + + warningContent.FadeIn(500, Easing.InQuint); + } + + protected override void PopOut() => this.FadeOut(500, Easing.InQuint); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 76020f5385..801feb7d01 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -6,17 +6,13 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -424,90 +420,5 @@ namespace osu.Game.Screens.Play } #endregion - - private class EpilepsyWarning : VisibilityContainer - { - private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); - - private Track track; - private FillFlowContainer warningContent; - - public EpilepsyWarning() - { - RelativeSizeAxes = Axes.Both; - Alpha = 0f; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, IBindable beatmap) - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - warningContent = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new SpriteIcon - { - Colour = colours.Yellow, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.ExclamationTriangle, - Size = new Vector2(50), - }, - new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.Centre, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }.With(tfc => - { - tfc.AddText("This beatmap contains scenes with "); - tfc.AddText("rapidly flashing colours", s => - { - s.Font = s.Font.With(weight: FontWeight.Bold); - s.Colour = colours.Yellow; - }); - tfc.AddText("."); - - tfc.NewParagraph(); - tfc.AddText("Please take caution if you are affected by epilepsy."); - }), - } - } - }; - - track = beatmap.Value.Track; - track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); - } - - protected override void PopIn() - { - this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); - - warningContent.FadeIn(500, Easing.InQuint); - } - - protected override void PopOut() => this.FadeOut(500, Easing.InQuint); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); - } - } } } From a9f27a71a2ff60b848632cf5595b354b49db760a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:53:41 +0200 Subject: [PATCH 107/322] Fix code formatting issues --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 6 +++--- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 341924ae6d..1efc71cad3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestPlayerLoaderContainer container; private TestPlayer player; - private bool EpilepsyWarning = false; + private bool epilepsyWarning; [Resolved] private AudioManager audioManager { get; set; } @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Beatmap.Value.BeatmapInfo.EpilepsyWarning = EpilepsyWarning; + Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning; foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); @@ -247,7 +247,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(false)] public void TestEpilepsyWarning(bool warning) { - AddStep("change epilepsy warning", () => EpilepsyWarning = warning); + AddStep("change epilepsy warning", () => epilepsyWarning = warning); AddStep("load dummy beatmap", () => ResetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index fd17e38a4f..36a3880890 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -175,6 +175,7 @@ namespace osu.Game.Beatmaps.Formats case @"WidescreenStoryboard": beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; + case @"EpilepsyWarning": beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; break; From 850590304142d76ccd59d130765283ec7020ce44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:08:05 +0200 Subject: [PATCH 108/322] Move warning construction to load() --- osu.Game/Screens/Play/PlayerLoader.cs | 34 ++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 801feb7d01..8d66eb67d3 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -84,14 +85,15 @@ namespace osu.Game.Screens.Play private bool hideOverlays; - private bool epilepsyShown; - private InputManager inputManager; private IdleTracker idleTracker; private ScheduledDelegate scheduledPushPlayer; + [CanBeNull] + private EpilepsyWarning epilepsyWarning; + [Resolved(CanBeNull = true)] private NotificationOverlay notificationOverlay { get; set; } @@ -140,6 +142,15 @@ namespace osu.Game.Screens.Play }, idleTracker = new IdleTracker(750) }); + + if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) + { + AddInternal(epilepsyWarning = new EpilepsyWarning + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } } protected override void LoadComplete() @@ -310,27 +321,18 @@ namespace osu.Game.Screens.Play { contentOut(); - if (true && !epilepsyShown) + if (epilepsyWarning != null) { - EpilepsyWarning warning; - - AddInternal(warning = new EpilepsyWarning - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = { Value = Visibility.Visible } - }); - - epilepsyShown = true; + epilepsyWarning.State.Value = Visibility.Visible; this.Delay(2000).Schedule(() => { - warning.Hide(); - warning.Expire(); + epilepsyWarning.Hide(); + epilepsyWarning.Expire(); }); } - this.Delay(epilepsyShown ? 2500 : 250).Schedule(() => + this.Delay(epilepsyWarning?.State.Value == Visibility.Visible ? 2500 : 250).Schedule(() => { if (!this.IsCurrentScreen()) return; From 1ac0b3b13d5cd90028b2fae6596741a01178c949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:08:26 +0200 Subject: [PATCH 109/322] Add asserts to tests --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1efc71cad3..38eca72bd1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Overlays; @@ -249,7 +250,10 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("change epilepsy warning", () => epilepsyWarning = warning); AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType().Any() == warning); } private class TestPlayerLoaderContainer : Container From 6e50ae045834f85310fa2943a12bd17e69fcbee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:22:30 +0200 Subject: [PATCH 110/322] Reformulate push sequence code --- osu.Game/Screens/Play/PlayerLoader.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 8d66eb67d3..eabe090725 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; @@ -321,18 +322,24 @@ namespace osu.Game.Screens.Play { contentOut(); + TransformSequence pushSequence; + if (epilepsyWarning != null) { epilepsyWarning.State.Value = Visibility.Visible; - this.Delay(2000).Schedule(() => + pushSequence = this.Delay(3000).Schedule(() => { epilepsyWarning.Hide(); epilepsyWarning.Expire(); }); } + else + { + pushSequence = this.Delay(0); + } - this.Delay(epilepsyWarning?.State.Value == Visibility.Visible ? 2500 : 250).Schedule(() => + pushSequence.Delay(250).Schedule(() => { if (!this.IsCurrentScreen()) return; From 1238e6c30fc27f905bdf9274fe0feb976b07cd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:46:08 +0200 Subject: [PATCH 111/322] Add flag value to database Unfortunately required, as loadBeatmaps() refreshes the decoded beatmap with the database-stored values, which can end up overwriting the decoded ones. --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 - ...01019224408_AddEpilepsyWarning.Designer.cs | 508 ++++++++++++++++++ .../20201019224408_AddEpilepsyWarning.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 2 + 4 files changed, 533 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b7946d53ca..1512240f8a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -90,8 +90,6 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } - - [NotMapped] public bool EpilepsyWarning { get; set; } // Editor diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs new file mode 100644 index 0000000000..1c05de832e --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20201019224408_AddEpilepsyWarning")] + partial class AddEpilepsyWarning + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs new file mode 100644 index 0000000000..be6968aa5d --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddEpilepsyWarning : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index bc4fc3342d..ec4461ca56 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -57,6 +57,8 @@ namespace osu.Game.Migrations b.Property("DistanceSpacing"); + b.Property("EpilepsyWarning"); + b.Property("GridSize"); b.Property("Hash"); From a164d330e57744b80f0834dfa8d5601c4baab9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:51:31 +0200 Subject: [PATCH 112/322] Improve feel of transition --- osu.Game/Screens/Play/EpilepsyWarning.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index e115c7e057..244a903bdd 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -89,12 +89,12 @@ namespace osu.Game.Screens.Play protected override void PopIn() { this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InSine); warningContent.FadeIn(500, Easing.InQuint); } - protected override void PopOut() => this.FadeOut(500, Easing.InQuint); + protected override void PopOut() => this.FadeOut(500, Easing.OutQuint); protected override void Dispose(bool isDisposing) { From 1fc22bdbffdace861ad701520e6cc7d44055d5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:59:30 +0200 Subject: [PATCH 113/322] Only show warning once on given map --- osu.Game/Screens/Play/PlayerLoader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index eabe090725..fb03f09d8e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -324,7 +324,9 @@ namespace osu.Game.Screens.Play TransformSequence pushSequence; - if (epilepsyWarning != null) + // only show if the warning was created (i.e. the beatmap needs it) + // and this is not a restart of the map (the warning expires after first load). + if (epilepsyWarning?.IsAlive == true) { epilepsyWarning.State.Value = Visibility.Visible; From 05251c646ed52a55027d558136f7f4a48a967ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 01:06:20 +0200 Subject: [PATCH 114/322] Fade volume back up on pop out --- osu.Game/Screens/Play/EpilepsyWarning.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 244a903bdd..051604f115 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -89,12 +89,14 @@ namespace osu.Game.Screens.Play protected override void PopIn() { this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InSine); + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); warningContent.FadeIn(500, Easing.InQuint); } - protected override void PopOut() => this.FadeOut(500, Easing.OutQuint); + protected override void PopOut() + => this.FadeOut(500, Easing.OutQuint) + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 1, 500, Easing.OutQuint); protected override void Dispose(bool isDisposing) { From c57fecd1fc4379b7d15e58c5612e8c425ddea375 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 12:43:57 +0900 Subject: [PATCH 115/322] Update comment to make it clear this is a hack --- osu.Game/Screens/Play/HUDOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 0a45df51a0..6425f8123d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -205,7 +205,9 @@ namespace osu.Game.Screens.Play { base.Update(); - // for now align with the accuracy counter. eventually this will be user customisable. + // HACK: for now align with the accuracy counter. + // this is done for the sake of hacky legacy skins which extend the health bar to take up the full screen area. + // it only works with the default skin due to padding offsetting it *just enough* to coexist. topRightElements.Y = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; bottomRightElements.Y = -Progress.Height; From 267b399f9f120f1d92500f775fbbbb949ee0290d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 13:59:03 +0900 Subject: [PATCH 116/322] Add some simple border styles --- .../Configuration/OsuRulesetConfigManager.cs | 5 +- .../Edit/OsuHitObjectComposer.cs | 14 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 8 +- .../UI/OsuSettingsSubsection.cs | 7 +- osu.Game/Rulesets/UI/PlayfieldBorder.cs | 137 ++++++++++++++++-- osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs | 12 ++ 6 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index da8767c017..e8272057f3 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -3,6 +3,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Configuration { @@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Configuration Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); Set(OsuRulesetSetting.ShowCursorTrail, true); - Set(OsuRulesetSetting.ShowPlayfieldBorder, false); + Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); } } @@ -28,6 +29,6 @@ namespace osu.Game.Rulesets.Osu.Configuration SnakingInSliders, SnakingOutSliders, ShowCursorTrail, - ShowPlayfieldBorder, + PlayfieldBorderStyle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index d9de9d2c5d..edd684d886 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -56,12 +56,18 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { - LayerBelowRuleset.Add(new PlayfieldBorder + LayerBelowRuleset.AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } + new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } + }, + distanceSnapGridContainer = new Container + { + RelativeSizeAxes = Axes.Both + } }); - LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 26fe686b0d..50727d590a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private readonly Bindable showPlayfieldBorder = new BindableBool(); + private readonly Bindable playfieldBorderStyle = new BindableBool(); private readonly IDictionary> poolDictionary = new Dictionary>(); @@ -90,13 +90,9 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager config) { - config?.BindWith(OsuRulesetSetting.ShowPlayfieldBorder, showPlayfieldBorder); - showPlayfieldBorder.BindValueChanged(updateBorderVisibility, true); + config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle); } - private void updateBorderVisibility(ValueChangedEvent settingChange) - => playfieldBorder.State.Value = settingChange.NewValue ? Visibility.Visible : Visibility.Hidden; - public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 28c609f412..705ba3e929 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { @@ -39,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.UI LabelText = "Cursor trail", Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, - new SettingsCheckbox + new SettingsEnumDropdown { - LabelText = "Show playfield border", - Current = config.GetBindable(OsuRulesetSetting.ShowPlayfieldBorder), + LabelText = "Playfield border style", + Current = config.GetBindable(OsuRulesetSetting.PlayfieldBorderStyle), }, }; } diff --git a/osu.Game/Rulesets/UI/PlayfieldBorder.cs b/osu.Game/Rulesets/UI/PlayfieldBorder.cs index 66371c89ad..c83d1e7842 100644 --- a/osu.Game/Rulesets/UI/PlayfieldBorder.cs +++ b/osu.Game/Rulesets/UI/PlayfieldBorder.cs @@ -1,9 +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; +using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.UI @@ -11,27 +15,136 @@ namespace osu.Game.Rulesets.UI /// /// Provides a border around the playfield. /// - public class PlayfieldBorder : VisibilityContainer + public class PlayfieldBorder : CompositeDrawable { - private const int fade_duration = 200; + public Bindable PlayfieldBorderStyle { get; } = new Bindable(); + + private const int fade_duration = 500; + + private const float corner_length = 0.05f; + private const float corner_thickness = 2; public PlayfieldBorder() { RelativeSizeAxes = Axes.Both; - Masking = true; - BorderColour = Color4.White; - BorderThickness = 2; - - InternalChild = new Box + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true + new Line(Direction.Horizontal) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + new Line(Direction.Horizontal) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + new Line(Direction.Horizontal) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + new Line(Direction.Horizontal) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, }; } - protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); + protected override void LoadComplete() + { + base.LoadComplete(); + + PlayfieldBorderStyle.BindValueChanged(updateStyle, true); + } + + private void updateStyle(ValueChangedEvent style) + { + switch (style.NewValue) + { + case UI.PlayfieldBorderStyle.None: + this.FadeOut(fade_duration, Easing.OutQuint); + foreach (var line in InternalChildren.OfType()) + line.TweenLength(0); + + break; + + case UI.PlayfieldBorderStyle.Corners: + this.FadeIn(fade_duration, Easing.OutQuint); + foreach (var line in InternalChildren.OfType()) + line.TweenLength(corner_length); + + break; + + case UI.PlayfieldBorderStyle.Full: + this.FadeIn(fade_duration, Easing.OutQuint); + foreach (var line in InternalChildren.OfType()) + line.TweenLength(0.5f); + + break; + } + } + + private class Line : Box + { + private readonly Direction direction; + + public Line(Direction direction) + { + this.direction = direction; + + Colour = Color4.White; + // starting in relative avoids the framework thinking it knows best and setting the width to 1 initially. + + switch (direction) + { + case Direction.Horizontal: + RelativeSizeAxes = Axes.X; + Size = new Vector2(0, corner_thickness); + break; + + case Direction.Vertical: + RelativeSizeAxes = Axes.Y; + Size = new Vector2(corner_thickness, 0); + break; + } + } + + public void TweenLength(float value) + { + switch (direction) + { + case Direction.Horizontal: + this.ResizeWidthTo(value, fade_duration, Easing.OutQuint); + break; + + case Direction.Vertical: + this.ResizeHeightTo(value, fade_duration, Easing.OutQuint); + break; + } + } + } } } diff --git a/osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs b/osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs new file mode 100644 index 0000000000..0a0aad884e --- /dev/null +++ b/osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs @@ -0,0 +1,12 @@ +// 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.UI +{ + public enum PlayfieldBorderStyle + { + None, + Corners, + Full + } +} From f9ca47ca86a2d7e40f6b05a46e3e9ddf5d85f37e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 13:59:07 +0900 Subject: [PATCH 117/322] Add test coverage --- .../TestPlayfieldBorder.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs b/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs new file mode 100644 index 0000000000..23d9d265be --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestPlayfieldBorder : OsuTestScene + { + public TestPlayfieldBorder() + { + Bindable playfieldBorderStyle = new Bindable(); + + AddStep("add drawables", () => + { + Child = new Container + { + Size = new Vector2(400, 300), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new PlayfieldBorder + { + PlayfieldBorderStyle = { BindTarget = playfieldBorderStyle } + } + } + }; + }); + + AddStep("Set none", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.None); + AddStep("Set corners", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Corners); + AddStep("Set full", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Full); + } + } +} From 024009e1749aee8c7a147c1ac2645080b88b25f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 14:19:15 +0900 Subject: [PATCH 118/322] Change default to "always visible" --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e95f8b571a..7d601c0cb9 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -90,7 +90,7 @@ namespace osu.Game.Configuration Set(OsuSetting.HitLighting, true); - Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.DuringGameplay); + Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.FadePlayfieldWhenHealthLow, true); From 4f8a755518d45ba9d4c40b717e0729cb8ebbac82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 14:20:44 +0900 Subject: [PATCH 119/322] Add "hide during gameplay" mode --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- osu.Game/Configuration/HUDVisibilityMode.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 55d6b38b66..6ec673704c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExternalHideDoesntAffectConfig() { - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.DuringGameplay; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; createNew(); diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs index 2b133b1bcf..b0b55dd811 100644 --- a/osu.Game/Configuration/HUDVisibilityMode.cs +++ b/osu.Game/Configuration/HUDVisibilityMode.cs @@ -9,8 +9,11 @@ namespace osu.Game.Configuration { Never, + [Description("Hide during gameplay")] + HideDuringGameplay, + [Description("Hide during breaks")] - DuringGameplay, + HideDuringBreaks, Always } From 4e57751ca1ebe5284a58d783bea35c6786e7b8df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:03:12 +0900 Subject: [PATCH 120/322] Fix background dim application to avoid overdraw, transition smoother --- osu.Game/Screens/Play/EpilepsyWarning.cs | 18 +++++++----------- osu.Game/Screens/Play/PlayerLoader.cs | 3 +++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 051604f115..960d549ab6 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -5,16 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Screens.Backgrounds; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -31,16 +29,13 @@ namespace osu.Game.Screens.Play Alpha = 0f; } + public BackgroundScreenBeatmap DimmableBackground { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, IBindable beatmap) { Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, warningContent = new FillFlowContainer { Anchor = Anchor.Centre, @@ -88,10 +83,11 @@ namespace osu.Game.Screens.Play protected override void PopIn() { - this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + this.TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, FADE_DURATION); - warningContent.FadeIn(500, Easing.InQuint); + DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint); + + this.FadeIn(FADE_DURATION, Easing.OutQuint); } protected override void PopOut() diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f68246c928..ef01072a62 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -167,6 +167,9 @@ namespace osu.Game.Screens.Play { base.OnEntering(last); + if (epilepsyWarning != null) + epilepsyWarning.DimmableBackground = Background; + content.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); From 7a68636f71195e9003e84d52b3f1e598e3089ad4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:03:33 +0900 Subject: [PATCH 121/322] Adjust fade sequence and durations to feel better --- osu.Game/Screens/Play/EpilepsyWarning.cs | 6 +++--- osu.Game/Screens/Play/PlayerLoader.cs | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 960d549ab6..1c8e8c2f4e 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Play { public class EpilepsyWarning : VisibilityContainer { + public const double FADE_DURATION = 500; + private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); private Track track; @@ -90,9 +92,7 @@ namespace osu.Game.Screens.Play this.FadeIn(FADE_DURATION, Easing.OutQuint); } - protected override void PopOut() - => this.FadeOut(500, Easing.OutQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 1, 500, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(FADE_DURATION); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ef01072a62..fae0bfb295 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -323,26 +323,29 @@ namespace osu.Game.Screens.Play { contentOut(); - TransformSequence pushSequence; + TransformSequence pushSequence = this.Delay(250); // only show if the warning was created (i.e. the beatmap needs it) // and this is not a restart of the map (the warning expires after first load). if (epilepsyWarning?.IsAlive == true) { - epilepsyWarning.State.Value = Visibility.Visible; + const double epilepsy_display_length = 3000; - pushSequence = this.Delay(3000).Schedule(() => + pushSequence.Schedule(() => { - epilepsyWarning.Hide(); - epilepsyWarning.Expire(); + epilepsyWarning.State.Value = Visibility.Visible; + + this.Delay(epilepsy_display_length).Schedule(() => + { + epilepsyWarning.Hide(); + epilepsyWarning.Expire(); + }); }); - } - else - { - pushSequence = this.Delay(0); + + pushSequence.Delay(epilepsy_display_length); } - pushSequence.Delay(250).Schedule(() => + pushSequence.Schedule(() => { if (!this.IsCurrentScreen()) return; From 411ae38605bd601336de5f01e88e48e65d2453be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:06:31 +0900 Subject: [PATCH 122/322] Remove unused using --- osu.Game/Rulesets/UI/PlayfieldBorder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/PlayfieldBorder.cs b/osu.Game/Rulesets/UI/PlayfieldBorder.cs index c83d1e7842..458b88c6db 100644 --- a/osu.Game/Rulesets/UI/PlayfieldBorder.cs +++ b/osu.Game/Rulesets/UI/PlayfieldBorder.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.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; From 55d08fad5cdca600405551729126dbfe2634f687 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:18:15 +0900 Subject: [PATCH 123/322] Remove unused field --- osu.Game/Screens/Play/EpilepsyWarning.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 1c8e8c2f4e..e3cf0cd227 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -23,7 +23,6 @@ namespace osu.Game.Screens.Play private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); private Track track; - private FillFlowContainer warningContent; public EpilepsyWarning() { @@ -38,7 +37,7 @@ namespace osu.Game.Screens.Play { Children = new Drawable[] { - warningContent = new FillFlowContainer + new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 2c7880e9d6118d01ca5c0150780f1cf136c357df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 18:27:03 +0200 Subject: [PATCH 124/322] Add failing test case --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 888a2f2197..9b31dd045a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -265,6 +265,26 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType().Any() == warning); + + if (warning) + { + AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25); + AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1); + } + } + + [Test] + public void TestEpilepsyWarningEarlyExit() + { + AddStep("set epilepsy warning", () => epilepsyWarning = true); + AddStep("load dummy beatmap", () => ResetPlayer(false)); + + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddUntilStep("wait for epilepsy warning", () => loader.ChildrenOfType().Single().Alpha > 0); + AddStep("exit early", () => loader.Exit()); + + AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1); } private class TestPlayerLoaderContainer : Container From e54836a63e4051bc517d1d044cfe566490fabf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 22:27:59 +0200 Subject: [PATCH 125/322] Use SkinnableSprite to avoid unnecessary reloads --- .../Drawables/DrawableStoryboardAnimation.cs | 52 +++++-------------- .../Drawables/DrawableStoryboardSprite.cs | 39 ++++---------- 2 files changed, 24 insertions(+), 67 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 963cf37fea..c3b6dde44b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -2,13 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -16,18 +15,10 @@ using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardAnimation : SkinReloadableDrawable, IFlippable, IVectorScalable + public class DrawableStoryboardAnimation : DrawableAnimation, IFlippable, IVectorScalable { public StoryboardAnimation Animation { get; } - private TextureAnimation drawableTextureAnimation; - - [Resolved] - private TextureStore storyboardTextureStore { get; set; } - - private readonly List texturePathsRaw = new List(); - private readonly List texturePaths = new List(); - private bool flipH; public bool FlipH @@ -119,48 +110,31 @@ namespace osu.Game.Storyboards.Drawables Animation = animation; Origin = animation.Origin; Position = animation.InitialPosition; + Loop = animation.LoopType == AnimationLoopType.LoopForever; LifetimeStart = animation.StartTime; LifetimeEnd = animation.EndTime; } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, TextureStore textureStore) { - InternalChild = drawableTextureAnimation = new TextureAnimation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Loop = Animation.LoopType == AnimationLoopType.LoopForever - }; - for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = Animation.Path.Replace(".", frame + "."); - texturePathsRaw.Add(Path.GetFileNameWithoutExtension(framePath)); - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - texturePaths.Add(path); + var storyboardPath = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + var frameSprite = storyboardPath != null + ? (Drawable)new Sprite + { + Texture = textureStore.Get(storyboardPath) + } + : new SkinnableSprite(framePath); // fall back to skin textures if not found in storyboard files. + + AddFrame(frameSprite, Animation.FrameDelay); } Animation.ApplyTransforms(this); } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - drawableTextureAnimation.ClearFrames(); - - for (var frame = 0; frame < Animation.FrameCount; frame++) - { - var texture = skin?.GetTexture(texturePathsRaw[frame]) ?? storyboardTextureStore?.Get(texturePaths[frame]); - - if (texture == null) - continue; - - drawableTextureAnimation.AddFrame(texture, Animation.FrameDelay); - } - } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index cd09cafbce..95774de898 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; @@ -15,17 +15,10 @@ using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardSprite : SkinReloadableDrawable, IFlippable, IVectorScalable + public class DrawableStoryboardSprite : CompositeDrawable, IFlippable, IVectorScalable { public StoryboardSprite Sprite { get; } - private Sprite drawableSprite; - - [Resolved] - private TextureStore storyboardTextureStore { get; set; } - - private string texturePath; - private bool flipH; public bool FlipH @@ -123,29 +116,19 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, TextureStore textureStore) { - InternalChild = drawableSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }; + var storyboardPath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + var sprite = storyboardPath != null + ? (Drawable)new Sprite + { + Texture = textureStore.Get(storyboardPath) + } + : new SkinnableSprite(Sprite.Path); // fall back to skin textures if not found in storyboard files. - texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + InternalChild = sprite.With(s => s.Anchor = s.Origin = Anchor.Centre); Sprite.ApplyTransforms(this); } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - var newTexture = skin?.GetTexture(Path.GetFileNameWithoutExtension(Sprite.Path)) ?? storyboardTextureStore?.Get(texturePath); - - if (drawableSprite.Texture == newTexture) return; - - drawableSprite.Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) - drawableSprite.Texture = newTexture; - } } } From cdd56ece871a9999288196a859033a44665a3c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:32:04 +0200 Subject: [PATCH 126/322] Read UseSkinSprites when decoding storyboards --- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 16 ++++++++++++++++ osu.Game/Storyboards/Storyboard.cs | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 269449ef80..e2550d1ca4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -48,6 +48,10 @@ namespace osu.Game.Beatmaps.Formats switch (section) { + case Section.General: + handleGeneral(storyboard, line); + return; + case Section.Events: handleEvents(line); return; @@ -60,6 +64,18 @@ namespace osu.Game.Beatmaps.Formats base.ParseLine(storyboard, section, line); } + private void handleGeneral(Storyboard storyboard, string line) + { + var pair = SplitKeyVal(line); + + switch (pair.Key) + { + case "UseSkinSprites": + storyboard.UseSkinSprites = pair.Value == "1"; + break; + } + } + private void handleEvents(string line) { var depth = 0; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index b0fb583d62..daafdf015d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -15,6 +15,11 @@ namespace osu.Game.Storyboards public BeatmapInfo BeatmapInfo = new BeatmapInfo(); + /// + /// Whether the storyboard can fall back to skin sprites in case no matching storyboard sprites are found. + /// + public bool UseSkinSprites { get; set; } + public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable)); public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0); From 58a54c5b6c71c01315dd5effeb33f08a61449db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:40:20 +0200 Subject: [PATCH 127/322] Utilise UseSkinSprites value in storyboard sprite logic --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 1 + .../Drawables/DrawableStoryboardAnimation.cs | 11 ++++------- .../Storyboards/Drawables/DrawableStoryboardSprite.cs | 11 ++++------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index ec461fa095..4bc28e6cef 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -15,6 +15,7 @@ namespace osu.Game.Storyboards.Drawables { public class DrawableStoryboard : Container { + [Cached] public Storyboard Storyboard { get; } protected override Container Content { get; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index c3b6dde44b..8382f91d1f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -117,19 +117,16 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) { for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = Animation.Path.Replace(".", frame + "."); var storyboardPath = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var frameSprite = storyboardPath != null - ? (Drawable)new Sprite - { - Texture = textureStore.Get(storyboardPath) - } - : new SkinnableSprite(framePath); // fall back to skin textures if not found in storyboard files. + var frameSprite = storyboard.UseSkinSprites && storyboardPath == null + ? (Drawable)new SkinnableSprite(framePath) + : new Sprite { Texture = textureStore.Get(storyboardPath) }; AddFrame(frameSprite, Animation.FrameDelay); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 95774de898..9599375c76 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -116,15 +116,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) { var storyboardPath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var sprite = storyboardPath != null - ? (Drawable)new Sprite - { - Texture = textureStore.Get(storyboardPath) - } - : new SkinnableSprite(Sprite.Path); // fall back to skin textures if not found in storyboard files. + var sprite = storyboard.UseSkinSprites && storyboardPath == null + ? (Drawable)new SkinnableSprite(Sprite.Path) + : new Sprite { Texture = textureStore.Get(storyboardPath) }; InternalChild = sprite.With(s => s.Anchor = s.Origin = Anchor.Centre); From 8c14c9e1c4eb36789b150d14e273e6b6ccf1f772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 22:42:47 +0200 Subject: [PATCH 128/322] Add basic test coverage --- .../TestSceneDrawableStoryboardSprite.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs new file mode 100644 index 0000000000..9501026edc --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Storyboards.Drawables; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneDrawableStoryboardSprite : SkinnableTestScene + { + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [Cached] + private Storyboard storyboard { get; set; } = new Storyboard(); + + [Test] + public void TestSkinSpriteDisallowedByDefault() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false); + + AddStep("create sprites", () => SetContents( + () => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + + assertSpritesFromSkin(false); + } + + [Test] + public void TestAllowLookupFromSkin() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + + AddStep("create sprites", () => SetContents( + () => createSprite(lookup_name, Anchor.Centre, Vector2.Zero))); + + assertSpritesFromSkin(true); + } + + private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) + => new DrawableStoryboardSprite( + new StoryboardSprite(lookupName, origin, initialPosition) + ).With(s => + { + s.LifetimeStart = double.MinValue; + s.LifetimeEnd = double.MaxValue; + }); + + private void assertSpritesFromSkin(bool fromSkin) => + AddAssert($"sprites are {(fromSkin ? "from skin" : "from storyboard")}", + () => this.ChildrenOfType() + .All(sprite => sprite.ChildrenOfType().Any() == fromSkin)); + } +} From 22112e4303e8f017730a8c87f8ccb03f8e07c537 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Oct 2020 23:09:39 +0900 Subject: [PATCH 129/322] Fix ISourcedFromTouch events being blocked by LoadingLayer --- osu.Game/Graphics/UserInterface/LoadingLayer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs index 35b33c3d03..200edf84a6 100644 --- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs +++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs @@ -44,6 +44,11 @@ namespace osu.Game.Graphics.UserInterface // blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer. case ScrollEvent _: return false; + + // blocking touch events causes the ISourcedFromTouch versions to not be fired, potentially impeding behaviour of drawables *above* the loading layer that may utilise these. + // note that this will not work well if touch handling elements are beneath the this loading layer (something to consider for the future). + case TouchEvent _: + return false; } return true; From 58c9e57a685d3cee372857f863aab575fd1cbf71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Oct 2020 17:17:23 +0200 Subject: [PATCH 130/322] Fix comment --- osu.Game/Graphics/UserInterface/LoadingLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs index 200edf84a6..c8c4424bee 100644 --- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs +++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface return false; // blocking touch events causes the ISourcedFromTouch versions to not be fired, potentially impeding behaviour of drawables *above* the loading layer that may utilise these. - // note that this will not work well if touch handling elements are beneath the this loading layer (something to consider for the future). + // note that this will not work well if touch handling elements are beneath this loading layer (something to consider for the future). case TouchEvent _: return false; } From 670775cecbe8d642a229b22d5854f0c2f519383d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 21 Oct 2020 18:57:48 +0200 Subject: [PATCH 131/322] Make beatmap wedge difficulty indicator color update dynamically. --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 26 +++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 2a3eb8c67a..6085e266d7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -39,6 +39,11 @@ namespace osu.Game.Screens.Select private readonly IBindable ruleset = new Bindable(); + [Resolved] + private BeatmapDifficultyManager difficultyManager { get; set; } + + private IBindable beatmapDifficulty; + protected BufferedWedgeInfo Info; public BeatmapInfoWedge() @@ -88,6 +93,11 @@ namespace osu.Game.Screens.Select if (beatmap == value) return; beatmap = value; + + beatmapDifficulty?.UnbindAll(); + beatmapDifficulty = difficultyManager.GetBindableDifficulty(beatmap.BeatmapInfo); + beatmapDifficulty.BindValueChanged(_ => updateDisplay()); + updateDisplay(); } } @@ -113,7 +123,7 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value) + LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value) { Shear = -Shear, Depth = Info?.Depth + 1 ?? 0 @@ -141,12 +151,14 @@ namespace osu.Game.Screens.Select private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; + private readonly StarDifficulty starDifficulty; - public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset) + public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, StarDifficulty difficulty) : base(pixelSnapping: true) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; + starDifficulty = difficulty; } [BackgroundDependencyLoader] @@ -190,7 +202,7 @@ namespace osu.Game.Screens.Select }, }, }, - new DifficultyColourBar(beatmapInfo) + new DifficultyColourBar(starDifficulty) { RelativeSizeAxes = Axes.Y, Width = 20, @@ -447,11 +459,11 @@ namespace osu.Game.Screens.Select private class DifficultyColourBar : Container { - private readonly BeatmapInfo beatmap; + private readonly StarDifficulty difficulty; - public DifficultyColourBar(BeatmapInfo beatmap) + public DifficultyColourBar(StarDifficulty difficulty) { - this.beatmap = beatmap; + this.difficulty = difficulty; } [BackgroundDependencyLoader] @@ -459,7 +471,7 @@ namespace osu.Game.Screens.Select { const float full_opacity_ratio = 0.7f; - var difficultyColour = colours.ForDifficultyRating(beatmap.DifficultyRating); + var difficultyColour = colours.ForDifficultyRating(difficulty.DifficultyRating); Children = new Drawable[] { From cf69eacae9164311f973d10eb60bebf96330456c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 21 Oct 2020 19:05:14 +0200 Subject: [PATCH 132/322] Make StarRatingDisplay dynamic. --- .../Ranking/Expanded/StarRatingDisplay.cs | 25 +++++++++++++++---- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 6 ++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 4b38b298f1..402ab99908 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.Ranking.Expanded { private readonly BeatmapInfo beatmap; + private StarDifficulty? difficulty; + /// /// Creates a new . /// @@ -31,20 +33,33 @@ namespace osu.Game.Screens.Ranking.Expanded public StarRatingDisplay(BeatmapInfo beatmap) { this.beatmap = beatmap; - AutoSizeAxes = Axes.Both; + } + + /// + /// Creates a new using an already computed . + /// + /// The already computed to display the star difficulty of. + public StarRatingDisplay(StarDifficulty starDifficulty) + { + difficulty = starDifficulty; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, BeatmapDifficultyManager difficultyManager) { - var starRatingParts = beatmap.StarDifficulty.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + AutoSizeAxes = Axes.Both; + + if (!difficulty.HasValue) + difficulty = difficultyManager.GetDifficulty(beatmap); + + var starRatingParts = difficulty.Value.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - ColourInfo backgroundColour = beatmap.DifficultyRating == DifficultyRating.ExpertPlus + ColourInfo backgroundColour = difficulty.Value.DifficultyRating == DifficultyRating.ExpertPlus ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(beatmap.DifficultyRating); + : (ColourInfo)colours.ForDifficultyRating(difficulty.Value.DifficultyRating); InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 6085e266d7..bdfcc2fd96 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select Shear = wedged_container_shear, Children = new[] { - createStarRatingDisplay(beatmapInfo).With(display => + createStarRatingDisplay(starDifficulty).With(display => { display.Anchor = Anchor.TopRight; display.Origin = Anchor.TopRight; @@ -305,8 +305,8 @@ namespace osu.Game.Screens.Select StatusPill.Hide(); } - private static Drawable createStarRatingDisplay(BeatmapInfo beatmapInfo) => beatmapInfo.StarDifficulty > 0 - ? new StarRatingDisplay(beatmapInfo) + private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 + ? new StarRatingDisplay(difficulty) { Margin = new MarginPadding { Bottom = 5 } } From 9753dab93bd1e71324be2a84cda27500877fb38e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:19:12 +0900 Subject: [PATCH 133/322] Remove IOnlineComponent and change existing components to use bindable flow --- .../Visual/Online/TestSceneFriendDisplay.cs | 11 +-- .../Online/TestSceneOnlineViewContainer.cs | 8 +- ...BeatmapManager_BeatmapOnlineLookupQueue.cs | 2 +- osu.Game/Online/API/APIAccess.cs | 73 +++++-------------- osu.Game/Online/API/DummyAPIAccess.cs | 40 +++------- osu.Game/Online/API/IAPIProvider.cs | 14 +--- osu.Game/Online/API/IOnlineComponent.cs | 10 --- osu.Game/Online/Leaderboards/Leaderboard.cs | 20 +++-- osu.Game/Online/OnlineViewContainer.cs | 28 ++++--- osu.Game/Overlays/AccountCreationOverlay.cs | 14 ++-- osu.Game/Overlays/DashboardOverlay.cs | 13 +++- osu.Game/Overlays/FullscreenOverlay.cs | 18 +---- osu.Game/Overlays/OverlayView.cs | 19 +++-- .../Sections/General/LoginSettings.cs | 21 ++++-- .../Overlays/Toolbar/ToolbarUserButton.cs | 23 ++++-- osu.Game/Screens/Multi/Multiplayer.cs | 25 +++---- .../Screens/Select/DifficultyRecommender.cs | 20 ++--- 17 files changed, 140 insertions(+), 219 deletions(-) delete mode 100644 osu.Game/Online/API/IOnlineComponent.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index 72033fc121..8280300caa 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOffline() { - AddStep("Populate", () => display.Users = getUsers()); + AddStep("Populate with offline test users", () => display.Users = getUsers()); } [Test] @@ -80,14 +80,7 @@ namespace osu.Game.Tests.Visual.Online private class TestFriendDisplay : FriendDisplay { - public void Fetch() - { - base.APIStateChanged(API, APIState.Online); - } - - public override void APIStateChanged(IAPIProvider api, APIState state) - { - } + public void Fetch() => PerformFetch(); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs index 9591d53b24..ec183adbbc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOnlineStateVisibility() { - AddStep("set status to online", () => ((DummyAPIAccess)API).State = APIState.Online); + AddStep("set status to online", () => ((DummyAPIAccess)API).SetState(APIState.Online)); AddUntilStep("children are visible", () => onlineView.ViewTarget.IsPresent); AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent); @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOfflineStateVisibility() { - AddStep("set status to offline", () => ((DummyAPIAccess)API).State = APIState.Offline); + AddStep("set status to offline", () => ((DummyAPIAccess)API).SetState(APIState.Offline)); AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent); AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent); @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestConnectingStateVisibility() { - AddStep("set status to connecting", () => ((DummyAPIAccess)API).State = APIState.Connecting); + AddStep("set status to connecting", () => ((DummyAPIAccess)API).SetState(APIState.Connecting)); AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent); AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent); @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestFailingStateVisibility() { - AddStep("set status to failing", () => ((DummyAPIAccess)API).State = APIState.Failing); + AddStep("set status to failing", () => ((DummyAPIAccess)API).SetState(APIState.Failing)); AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent); AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent); diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index cb4884aa51..c4563d5844 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps if (checkLocalCache(set, beatmap)) return; - if (api?.State != APIState.Online) + if (api?.State.Value != APIState.Online) return; var req = new GetBeatmapRequest(beatmap); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 4ea5c192fe..b916339a53 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -78,26 +78,8 @@ namespace osu.Game.Online.API private void onTokenChanged(ValueChangedEvent e) => config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); - private readonly List components = new List(); - internal new void Schedule(Action action) => base.Schedule(action); - /// - /// Register a component to receive API events. - /// Fires once immediately to ensure a correct state. - /// - /// - public void Register(IOnlineComponent component) - { - Schedule(() => components.Add(component)); - component.APIStateChanged(this, state); - } - - public void Unregister(IOnlineComponent component) - { - Schedule(() => components.Remove(component)); - } - public string AccessToken => authentication.RequestAccessToken(); /// @@ -109,7 +91,7 @@ namespace osu.Game.Online.API { while (!cancellationToken.IsCancellationRequested) { - switch (State) + switch (State.Value) { case APIState.Failing: //todo: replace this with a ping request. @@ -131,12 +113,12 @@ namespace osu.Game.Online.API // work to restore a connection... if (!HasLogin) { - State = APIState.Offline; + state.Value = APIState.Offline; Thread.Sleep(50); continue; } - State = APIState.Connecting; + state.Value = APIState.Connecting; // save the username at this point, if the user requested for it to be. config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); @@ -162,20 +144,20 @@ namespace osu.Game.Online.API failureCount = 0; //we're connected! - State = APIState.Online; + state.Value = APIState.Online; }; if (!handleRequest(userReq)) { - if (State == APIState.Connecting) - State = APIState.Failing; + if (State.Value == APIState.Connecting) + state.Value = APIState.Failing; continue; } // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests // before actually going online. - while (State > APIState.Offline && State < APIState.Online) + while (State.Value > APIState.Offline && State.Value < APIState.Online) Thread.Sleep(500); break; @@ -224,7 +206,7 @@ namespace osu.Game.Online.API public void Login(string username, string password) { - Debug.Assert(State == APIState.Offline); + Debug.Assert(State.Value == APIState.Offline); ProvidedUsername = username; this.password = password; @@ -232,7 +214,7 @@ namespace osu.Game.Online.API public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) { - Debug.Assert(State == APIState.Offline); + Debug.Assert(State.Value == APIState.Offline); var req = new RegistrationRequest { @@ -276,7 +258,7 @@ namespace osu.Game.Online.API req.Perform(this); // we could still be in initialisation, at which point we don't want to say we're Online yet. - if (IsLoggedIn) State = APIState.Online; + if (IsLoggedIn) state.Value = APIState.Online; failureCount = 0; return true; @@ -293,27 +275,12 @@ namespace osu.Game.Online.API } } - private APIState state; + private readonly Bindable state = new Bindable(); - public APIState State - { - get => state; - private set - { - if (state == value) - return; - - APIState oldState = state; - state = value; - - log.Add($@"We just went {state}!"); - Schedule(() => - { - components.ForEach(c => c.APIStateChanged(this, state)); - OnStateChange?.Invoke(oldState, state); - }); - } - } + /// + /// The current connectivity state of the API. + /// + public IBindable State => state; private bool handleWebException(WebException we) { @@ -343,9 +310,9 @@ namespace osu.Game.Online.API // we might try again at an api level. return false; - if (State == APIState.Online) + if (State.Value == APIState.Online) { - State = APIState.Failing; + state.Value = APIState.Failing; flushQueue(); } @@ -362,10 +329,6 @@ namespace osu.Game.Online.API lock (queue) queue.Enqueue(request); } - public event StateChangeDelegate OnStateChange; - - public delegate void StateChangeDelegate(APIState oldState, APIState newState); - private void flushQueue(bool failOldRequests = true) { lock (queue) @@ -392,7 +355,7 @@ namespace osu.Game.Online.API // Scheduled prior to state change such that the state changed event is invoked with the correct user present Schedule(() => LocalUser.Value = createGuestUser()); - State = APIState.Offline; + state.Value = APIState.Offline; } private static User createGuestUser() => new GuestUser(); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 7800241904..1672d0495d 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -21,34 +21,23 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); - public bool IsLoggedIn => State == APIState.Online; + public bool IsLoggedIn => State.Value == APIState.Online; public string ProvidedUsername => LocalUser.Value.Username; public string Endpoint => "http://localhost"; - private APIState state = APIState.Online; - - private readonly List components = new List(); - /// /// Provide handling logic for an arbitrary API request. /// public Action HandleRequest; - public APIState State - { - get => state; - set - { - if (state == value) - return; + private readonly Bindable state = new Bindable(APIState.Online); - state = value; - - Scheduler.Add(() => components.ForEach(c => c.APIStateChanged(this, value))); - } - } + /// + /// The current connectivity state of the API. + /// + public IBindable State => state; public DummyAPIAccess() { @@ -72,17 +61,6 @@ namespace osu.Game.Online.API return Task.CompletedTask; } - public void Register(IOnlineComponent component) - { - Scheduler.Add(delegate { components.Add(component); }); - component.APIStateChanged(this, state); - } - - public void Unregister(IOnlineComponent component) - { - Scheduler.Add(delegate { components.Remove(component); }); - } - public void Login(string username, string password) { LocalUser.Value = new User @@ -91,13 +69,13 @@ namespace osu.Game.Online.API Id = 1001, }; - State = APIState.Online; + state.Value = APIState.Online; } public void Logout() { LocalUser.Value = new GuestUser(); - State = APIState.Offline; + state.Value = APIState.Offline; } public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) @@ -105,5 +83,7 @@ namespace osu.Game.Online.API Thread.Sleep(200); return null; } + + public void SetState(APIState newState) => state.Value = newState; } } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index dff6d0b2ce..256d2ed151 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online.API /// string Endpoint { get; } - APIState State { get; } + IBindable State { get; } /// /// Queue a new request. @@ -61,18 +61,6 @@ namespace osu.Game.Online.API /// The request to perform. Task PerformAsync(APIRequest request); - /// - /// Register a component to receive state changes. - /// - /// The component to register. - void Register(IOnlineComponent component); - - /// - /// Unregisters a component to receive state changes. - /// - /// The component to unregister. - void Unregister(IOnlineComponent component); - /// /// Attempt to login using the provided credentials. This is a non-blocking operation. /// diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs deleted file mode 100644 index da6b784759..0000000000 --- a/osu.Game/Online/API/IOnlineComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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.Online.API -{ - public interface IOnlineComponent - { - void APIStateChanged(IAPIProvider api, APIState state); - } -} diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 2acee394a6..3a5c2e181f 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -22,7 +23,7 @@ using osuTK.Graphics; namespace osu.Game.Online.Leaderboards { - public abstract class Leaderboard : Container, IOnlineComponent + public abstract class Leaderboard : Container { private const double fade_duration = 300; @@ -242,16 +243,13 @@ namespace osu.Game.Online.Leaderboards private ScheduledDelegate pendingUpdateScores; + private readonly IBindable apiState = new Bindable(); + [BackgroundDependencyLoader] private void load() { - api?.Register(this); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - api?.Unregister(this); + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); } public void RefreshScores() => UpdateScores(); @@ -260,9 +258,9 @@ namespace osu.Game.Online.Leaderboards protected abstract bool IsOnlineScope { get; } - public void APIStateChanged(IAPIProvider api, APIState state) + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { - switch (state) + switch (state.NewValue) { case APIState.Online: case APIState.Offline: @@ -271,7 +269,7 @@ namespace osu.Game.Online.Leaderboards break; } - } + }); protected void UpdateScores() { diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index b52e3d9e3c..295d079d29 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -14,7 +15,7 @@ namespace osu.Game.Online /// A for displaying online content which require a local user to be logged in. /// Shows its children only when the local user is logged in and supports displaying a placeholder if not. /// - public abstract class OnlineViewContainer : Container, IOnlineComponent + public abstract class OnlineViewContainer : Container { protected LoadingSpinner LoadingSpinner { get; private set; } @@ -34,8 +35,10 @@ namespace osu.Game.Online this.placeholderMessage = placeholderMessage; } + private readonly IBindable apiState = new Bindable(); + [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api) { InternalChildren = new Drawable[] { @@ -46,18 +49,19 @@ namespace osu.Game.Online Alpha = 0, } }; + + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - - API.Register(this); } - public virtual void APIStateChanged(IAPIProvider api, APIState state) + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { - switch (state) + switch (state.NewValue) { case APIState.Offline: PopContentOut(Content); @@ -79,7 +83,7 @@ namespace osu.Game.Online placeholder.FadeOut(transform_duration / 2, Easing.OutQuint); break; } - } + }); /// /// Applies a transform to the online content to make it hidden. @@ -90,11 +94,5 @@ namespace osu.Game.Online /// Applies a transform to the online content to make it visible. /// protected virtual void PopContentIn(Drawable content) => content.FadeIn(transform_duration, Easing.OutQuint); - - protected override void Dispose(bool isDisposing) - { - API?.Unregister(this); - base.Dispose(isDisposing); - } } } diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 89d8cbde11..58ede5502a 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class AccountCreationOverlay : OsuFocusedOverlayContainer, IOnlineComponent + public class AccountCreationOverlay : OsuFocusedOverlayContainer { private const float transition_time = 400; @@ -30,10 +31,13 @@ namespace osu.Game.Overlays Origin = Anchor.Centre; } + private readonly IBindable apiState = new Bindable(); + [BackgroundDependencyLoader] private void load(OsuColour colours, IAPIProvider api) { - api.Register(this); + apiState.BindTo(api.State); + apiState.BindValueChanged(apiStateChanged, true); Children = new Drawable[] { @@ -97,9 +101,9 @@ namespace osu.Game.Overlays this.FadeOut(100); } - public void APIStateChanged(IAPIProvider api, APIState state) + private void apiStateChanged(ValueChangedEvent state) => Schedule(() => { - switch (state) + switch (state.NewValue) { case APIState.Offline: case APIState.Failing: @@ -112,6 +116,6 @@ namespace osu.Game.Overlays Hide(); break; } - } + }); } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 8135b83a03..6c58ed50fb 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -33,6 +33,15 @@ namespace osu.Game.Overlays { } + private readonly IBindable apiState = new Bindable(); + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); + } + [BackgroundDependencyLoader] private void load() { @@ -130,13 +139,13 @@ namespace osu.Game.Overlays } } - public override void APIStateChanged(IAPIProvider api, APIState state) + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { if (State.Value == Visibility.Hidden) return; Header.Current.TriggerChange(); - } + }); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index bd6b07c65f..6f56d95929 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -12,7 +12,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent, INamedOverlayComponent + public abstract class FullscreenOverlay : WaveOverlayContainer, INamedOverlayComponent where T : OverlayHeader { public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty; @@ -86,21 +86,5 @@ namespace osu.Game.Overlays protected virtual void PopOutComplete() { } - - protected override void LoadComplete() - { - base.LoadComplete(); - API.Register(this); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - API?.Unregister(this); - } - - public virtual void APIStateChanged(IAPIProvider api, APIState state) - { - } } } diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index 312271316a..c254cdf290 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays /// Automatically performs a data fetch on load. /// /// The type of the API response. - public abstract class OverlayView : CompositeDrawable, IOnlineComponent + public abstract class OverlayView : CompositeDrawable where T : class { [Resolved] @@ -29,10 +30,13 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y; } - protected override void LoadComplete() + private readonly IBindable apiState = new Bindable(); + + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - API.Register(this); + apiState.BindTo(API.State); + apiState.BindValueChanged(onlineStateChanged, true); } /// @@ -59,20 +63,19 @@ namespace osu.Game.Overlays API.Queue(request); } - public virtual void APIStateChanged(IAPIProvider api, APIState state) + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { - switch (state) + switch (state.NewValue) { case APIState.Online: PerformFetch(); break; } - } + }); protected override void Dispose(bool isDisposing) { request?.Cancel(); - API?.Unregister(this); base.Dispose(isDisposing); } } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 9e358d0cf5..873272bf12 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -13,6 +13,7 @@ using osu.Game.Online.API; using osuTK; using osu.Game.Users; using System.ComponentModel; +using osu.Framework.Bindables; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; @@ -25,7 +26,7 @@ using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Overlays.Settings.Sections.General { - public class LoginSettings : FillFlowContainer, IOnlineComponent + public class LoginSettings : FillFlowContainer { private bool bounding = true; private LoginForm form; @@ -41,6 +42,11 @@ namespace osu.Game.Overlays.Settings.Sections.General /// public Action RequestHide; + private readonly IBindable apiState = new Bindable(); + + [Resolved] + private IAPIProvider api { get; set; } + public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty; public bool Bounding @@ -61,17 +67,18 @@ namespace osu.Game.Overlays.Settings.Sections.General Spacing = new Vector2(0f, 5f); } - [BackgroundDependencyLoader(permitNulls: true)] - private void load(IAPIProvider api) + [BackgroundDependencyLoader] + private void load() { - api?.Register(this); + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); } - public void APIStateChanged(IAPIProvider api, APIState state) => Schedule(() => + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { form = null; - switch (state) + switch (state.NewValue) { case APIState.Offline: Children = new Drawable[] @@ -107,7 +114,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Origin = Anchor.TopCentre, TextAnchor = Anchor.TopCentre, AutoSizeAxes = Axes.Both, - Text = state == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ", + Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ", Margin = new MarginPadding { Top = 10, Bottom = 10 }, }, }; diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index bccef3d9fe..b21bc49a11 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; @@ -14,10 +15,15 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { - public class ToolbarUserButton : ToolbarOverlayToggleButton, IOnlineComponent + public class ToolbarUserButton : ToolbarOverlayToggleButton { private readonly UpdateableAvatar avatar; + [Resolved] + private IAPIProvider api { get; set; } + + private readonly IBindable apiState = new Bindable(); + public ToolbarUserButton() { AutoSizeAxes = Axes.X; @@ -43,17 +49,22 @@ namespace osu.Game.Overlays.Toolbar }); } - [BackgroundDependencyLoader(true)] - private void load(IAPIProvider api, LoginOverlay login) + [BackgroundDependencyLoader(permitNulls: true)] + private void load() { - api.Register(this); + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); + } + [BackgroundDependencyLoader(true)] + private void load(LoginOverlay login) + { StateContainer = login; } - public void APIStateChanged(IAPIProvider api, APIState state) + private void onlineStateChanged(ValueChangedEvent state) { - switch (state) + switch (state.NewValue) { default: Text = @"Guest"; diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 27f774e9ec..e6abde4d43 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -29,7 +29,7 @@ using osuTK; namespace osu.Game.Screens.Multi { [Cached] - public class Multiplayer : OsuScreen, IOnlineComponent + public class Multiplayer : OsuScreen { public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true; @@ -146,15 +146,24 @@ namespace osu.Game.Screens.Multi screenStack.ScreenExited += screenExited; } + private readonly IBindable apiState = new Bindable(); + [BackgroundDependencyLoader(true)] private void load(IdleTracker idleTracker) { - api.Register(this); + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); } + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => + { + if (state.NewValue != APIState.Online) + Schedule(forcefullyExit); + }); + protected override void LoadComplete() { base.LoadComplete(); @@ -199,12 +208,6 @@ namespace osu.Game.Screens.Multi Logger.Log($"Polling adjusted (listing: {roomManager.TimeBetweenListingPolls}, selection: {roomManager.TimeBetweenSelectionPolls})"); } - public void APIStateChanged(IAPIProvider api, APIState state) - { - if (state != APIState.Online) - Schedule(forcefullyExit); - } - private void forcefullyExit() { // This is temporary since we don't currently have a way to force screens to be exited @@ -371,12 +374,6 @@ namespace osu.Game.Screens.Multi } } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - api?.Unregister(this); - } - private class MultiplayerWaveContainer : WaveContainer { protected override bool StartHidden => true; diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs index 0dd3341a93..ff54e0a8df 100644 --- a/osu.Game/Screens/Select/DifficultyRecommender.cs +++ b/osu.Game/Screens/Select/DifficultyRecommender.cs @@ -15,7 +15,7 @@ using osu.Game.Rulesets; namespace osu.Game.Screens.Select { - public class DifficultyRecommender : Component, IOnlineComponent + public class DifficultyRecommender : Component { [Resolved] private IAPIProvider api { get; set; } @@ -28,10 +28,13 @@ namespace osu.Game.Screens.Select private readonly Dictionary recommendedStarDifficulty = new Dictionary(); + private readonly IBindable apiState = new Bindable(); + [BackgroundDependencyLoader] private void load() { - api.Register(this); + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); } /// @@ -72,21 +75,14 @@ namespace osu.Game.Screens.Select }); } - public void APIStateChanged(IAPIProvider api, APIState state) + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { - switch (state) + switch (state.NewValue) { case APIState.Online: calculateRecommendedDifficulties(); break; } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - api?.Unregister(this); - } + }); } } From 303975ccb152823110feea9b5dd8ced8983dca72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:27:49 +0900 Subject: [PATCH 134/322] Remove unnecessary permitNulls --- osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index b21bc49a11..fc47eced77 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Toolbar }); } - [BackgroundDependencyLoader(permitNulls: true)] + [BackgroundDependencyLoader] private void load() { apiState.BindTo(api.State); From 3fe6f77444eae95d06373b93a5035764e012728f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:30:49 +0900 Subject: [PATCH 135/322] Fix cases of multiple bdl methods --- osu.Game/Online/OnlineViewContainer.cs | 5 ----- osu.Game/Overlays/DashboardOverlay.cs | 4 ---- osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 10 +++------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index 295d079d29..c9fb70f0cc 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -54,11 +54,6 @@ namespace osu.Game.Online apiState.BindValueChanged(onlineStateChanged, true); } - [BackgroundDependencyLoader] - private void load() - { - } - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { switch (state.NewValue) diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 6c58ed50fb..a2490365e4 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -40,11 +40,7 @@ namespace osu.Game.Overlays { apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); - } - [BackgroundDependencyLoader] - private void load() - { Children = new Drawable[] { new Box diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index fc47eced77..9417224049 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -49,16 +49,12 @@ namespace osu.Game.Overlays.Toolbar }); } - [BackgroundDependencyLoader] - private void load() - { - apiState.BindTo(api.State); - apiState.BindValueChanged(onlineStateChanged, true); - } - [BackgroundDependencyLoader(true)] private void load(LoginOverlay login) { + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); + StateContainer = login; } From da573c74870762dd15dcbec52c998e2a5a2567c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:43:47 +0900 Subject: [PATCH 136/322] Remove unused usings --- .../Visual/Online/TestSceneFriendDisplay.cs | 13 ++++++------- osu.Game/Online/API/DummyAPIAccess.cs | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index 8280300caa..0cc6e9f358 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -3,14 +3,13 @@ using System; using System.Collections.Generic; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays.Dashboard.Friends; -using osu.Framework.Graphics; -using osu.Game.Users; -using osu.Game.Overlays; -using osu.Framework.Allocation; using NUnit.Framework; -using osu.Game.Online.API; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Dashboard.Friends; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 1672d0495d..da22a70bf8 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using osu.Framework.Bindables; From b39a4da6bcec029855c513964a5d29fab3a35977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Oct 2020 19:05:20 +0900 Subject: [PATCH 137/322] Add initial classes for spectator support --- .../Gameplay/TestSceneReplayRecorder.cs | 20 +++++- osu.Game/Online/Spectator/FrameDataBundle.cs | 17 +++++ osu.Game/Online/Spectator/ISpectatorClient.cs | 14 ++++ osu.Game/Online/Spectator/ISpectatorServer.cs | 14 ++++ osu.Game/Online/Spectator/SpectatorClient.cs | 70 +++++++++++++++++++ osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 2 + osu.Game/osu.Game.csproj | 3 + 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Spectator/FrameDataBundle.cs create mode 100644 osu.Game/Online/Spectator/ISpectatorClient.cs create mode 100644 osu.Game/Online/Spectator/ISpectatorServer.cs create mode 100644 osu.Game/Online/Spectator/SpectatorClient.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index bc1c10e59d..88a4024576 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,7 +15,9 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Spectator; using osu.Game.Replays; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; @@ -260,13 +264,27 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { + private readonly SpectatorClient client; + public TestReplayRecorder(Replay target) : base(target) { + var connection = new HubConnectionBuilder() + .WithUrl("http://localhost:5009/spectator") + .AddMessagePackProtocol() + // .ConfigureLogging(logging => { logging.AddConsole(); }) + .Build(); + + connection.StartAsync().Wait(); + + client = new SpectatorClient(connection); } protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) - => new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + { + client.SendFrames(new FrameDataBundle(new[] { new LegacyReplayFrame(Time.Current, mousePosition.X, mousePosition.Y, ReplayButtonState.None) })); + return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + } } } } diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs new file mode 100644 index 0000000000..67f2688289 --- /dev/null +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using osu.Game.Replays.Legacy; + +namespace osu.Game.Online.Spectator +{ + [Serializable] + public class FrameDataBundle + { + public IEnumerable Frames { get; set; } + + public FrameDataBundle(IEnumerable frames) + { + Frames = frames; + } + } +} diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs new file mode 100644 index 0000000000..4741d7409a --- /dev/null +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using osu.Game.Online.Spectator; + +namespace osu.Server.Spectator.Hubs +{ + public interface ISpectatorClient + { + Task UserBeganPlaying(string userId, int beatmapId); + + Task UserFinishedPlaying(string userId, int beatmapId); + + Task UserSentFrames(string userId, FrameDataBundle data); + } +} \ No newline at end of file diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs new file mode 100644 index 0000000000..1dcde30221 --- /dev/null +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace osu.Game.Online.Spectator +{ + public interface ISpectatorServer + { + Task BeginPlaySession(int beatmapId); + Task SendFrameData(FrameDataBundle data); + Task EndPlaySession(int beatmapId); + + Task StartWatchingUser(string userId); + Task EndWatchingUser(string userId); + } +} diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs new file mode 100644 index 0000000000..c1414f7914 --- /dev/null +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; +using osu.Server.Spectator.Hubs; + +namespace osu.Game.Online.Spectator +{ + public class SpectatorClient : ISpectatorClient + { + private readonly HubConnection connection; + + private readonly List watchingUsers = new List(); + + public SpectatorClient(HubConnection connection) + { + this.connection = connection; + + // this is kind of SILLY + // 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); + } + + Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) + { + if (connection.ConnectionId != userId) + { + if (watchingUsers.Contains(userId)) + { + Console.WriteLine($"{connection.ConnectionId} received began playing for already watched user {userId}"); + } + else + { + Console.WriteLine($"{connection.ConnectionId} requesting watch other user {userId}"); + WatchUser(userId); + watchingUsers.Add(userId); + } + } + else + { + Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {beatmapId}"); + } + + return Task.CompletedTask; + } + + Task ISpectatorClient.UserFinishedPlaying(string userId, int beatmapId) + { + Console.WriteLine($"{connection.ConnectionId} Received user finished event {beatmapId}"); + return Task.CompletedTask; + } + + Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) + { + Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First().ToString()}"); + return Task.CompletedTask; + } + + public Task BeginPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + + public Task SendFrames(FrameDataBundle data) => connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + + public Task EndPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + + private Task WatchUser(string userId) => connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + } +} diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index c3cffa8699..656fd1814e 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.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 MessagePack; using osu.Game.Rulesets.Replays; using osuTK; @@ -8,6 +9,7 @@ namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { + [IgnoreMember] public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); public float? MouseX; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index de7bde824f..fd010fcc43 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,6 +21,9 @@ + + + From db4dd3182b2494f6aa641855a32928be9465c9d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 13:12:58 +0900 Subject: [PATCH 138/322] Add xmldoc to spectator interfaces --- osu.Game/Online/Spectator/ISpectatorClient.cs | 23 +++++++++++++-- osu.Game/Online/Spectator/ISpectatorServer.cs | 28 +++++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 1 - 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index 4741d7409a..ed762ac1fe 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -1,14 +1,31 @@ using System.Threading.Tasks; -using osu.Game.Online.Spectator; -namespace osu.Server.Spectator.Hubs +namespace osu.Game.Online.Spectator { + /// + /// An interface defining a spectator client instance. + /// public interface ISpectatorClient { + /// + /// Signals that a user has begun a new play session. + /// + /// The user. + /// The beatmap the user is playing. Task UserBeganPlaying(string userId, int beatmapId); + /// + /// Signals that a user has finished a play session. + /// + /// The user. + /// The beatmap the user has finished playing. Task UserFinishedPlaying(string userId, int beatmapId); + /// + /// Called when new frames are available for a subscribed user's play session. + /// + /// The user. + /// The frame data. Task UserSentFrames(string userId, FrameDataBundle data); } -} \ No newline at end of file +} diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 1dcde30221..03ca37d524 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -2,13 +2,41 @@ using System.Threading.Tasks; namespace osu.Game.Online.Spectator { + /// + /// An interface defining the spectator server instance. + /// public interface ISpectatorServer { + /// + /// Signal the start of a new play session. + /// + /// The beatmap currently being played. Eventually this should be replaced with more complete metadata. Task BeginPlaySession(int beatmapId); + + /// + /// Send a bundle of frame data for the current play session. + /// + /// The frame data. Task SendFrameData(FrameDataBundle data); + + /// + /// Signal the end of a play session. + /// + /// The beatmap that was completed. This should be replaced with a play token once that flow is established. Task EndPlaySession(int beatmapId); + /// + /// Request spectating data for the specified user. May be called on multiple users and offline users. + /// For offline users, a subscription will be created and data will begin streaming on next play. + /// + /// The user to subscribe to. + /// Task StartWatchingUser(string userId); + + /// + /// Stop requesting spectating data for the specified user. Unsubscribes from receiving further data. + /// + /// The user to unsubscribe from. Task EndWatchingUser(string userId); } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index c1414f7914..4558699618 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; -using osu.Server.Spectator.Hubs; namespace osu.Game.Online.Spectator { From 5a00a05a95d3c7c1dba85a8beb2e4758da321942 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:49:48 +0900 Subject: [PATCH 139/322] Add missing schedule call --- osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 9417224049..db4e491d9a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Toolbar StateContainer = login; } - private void onlineStateChanged(ValueChangedEvent state) + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { switch (state.NewValue) { @@ -72,6 +72,6 @@ namespace osu.Game.Overlays.Toolbar avatar.User = api.LocalUser.Value; break; } - } + }); } } From c6db832efa940ee40d238fc036ab67b2f51f2c76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:56:20 +0900 Subject: [PATCH 140/322] Add xmldoc notes about thread safety of api bindables --- osu.Game/Online/API/IAPIProvider.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 256d2ed151..fc675639bf 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -11,11 +11,13 @@ namespace osu.Game.Online.API { /// /// The local user. + /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. /// Bindable LocalUser { get; } /// /// The current user's activity. + /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. /// Bindable Activity { get; } @@ -35,6 +37,10 @@ namespace osu.Game.Online.API /// string Endpoint { get; } + /// + /// The current connection state of the API. + /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. + /// IBindable State { get; } /// From 93db75bd414e90bfd9eb655f3b022bdface0f3cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 13:41:54 +0900 Subject: [PATCH 141/322] Begin shaping the spectator streaming component --- .../Gameplay/TestSceneReplayRecorder.cs | 14 ----- ...rClient.cs => SpectatorStreamingClient.cs} | 60 ++++++++++++++++--- osu.Game/OsuGameBase.cs | 7 ++- 3 files changed, 58 insertions(+), 23 deletions(-) rename osu.Game/Online/Spectator/{SpectatorClient.cs => SpectatorStreamingClient.cs} (58%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 88a4024576..71cd39953c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -264,25 +262,13 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { - private readonly SpectatorClient client; - public TestReplayRecorder(Replay target) : base(target) { - var connection = new HubConnectionBuilder() - .WithUrl("http://localhost:5009/spectator") - .AddMessagePackProtocol() - // .ConfigureLogging(logging => { logging.AddConsole(); }) - .Build(); - - connection.StartAsync().Wait(); - - client = new SpectatorClient(connection); } protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) { - client.SendFrames(new FrameDataBundle(new[] { new LegacyReplayFrame(Time.Current, mousePosition.X, mousePosition.Y, ReplayButtonState.None) })); return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs similarity index 58% rename from osu.Game/Online/Spectator/SpectatorClient.cs rename to osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 4558699618..a1a4a2774a 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -3,24 +3,70 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Online.API; namespace osu.Game.Online.Spectator { - public class SpectatorClient : ISpectatorClient + public class SpectatorStreamingClient : Component, ISpectatorClient { - private readonly HubConnection connection; + private HubConnection connection; private readonly List watchingUsers = new List(); - public SpectatorClient(HubConnection connection) - { - this.connection = connection; + private readonly IBindable apiState = new Bindable(); - // this is kind of SILLY - // https://github.com/dotnet/aspnetcore/issues/15198 + [Resolved] + private APIAccess api { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + apiState.BindTo(api.State); + apiState.BindValueChanged(apiStateChanged, true); + } + + private void apiStateChanged(ValueChangedEvent state) + { + switch (state.NewValue) + { + case APIState.Failing: + case APIState.Offline: + connection?.StopAsync(); + connection = null; + break; + + case APIState.Online: + connect(); + break; + } + } + +#if DEBUG + private const string endpoint = "http://localhost:5009/spectator"; +#else + private const string endpoint = "https://spectator.ppy.sh/spectator"; +#endif + + private void connect() + { + connection = new HubConnectionBuilder() + .WithUrl(endpoint, options => + { + options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); + }) + .AddMessagePackProtocol() + .Build(); + + // 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); + + connection.StartAsync(); } Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2d609668af..9b43d18a88 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,6 +30,7 @@ using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; +using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Resources; using osu.Game.Rulesets; @@ -74,6 +75,8 @@ namespace osu.Game protected IAPIProvider API; + private SpectatorStreamingClient spectatorStreaming; + protected MenuCursorContainer MenuCursorContainer; protected MusicController MusicController; @@ -189,9 +192,9 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); dependencies.CacheAs(SkinManager); - API ??= new APIAccess(LocalConfig); + dependencies.CacheAs(API ??= new APIAccess(LocalConfig)); - dependencies.CacheAs(API); + dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient()); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); From 175fd512b00f26402023dcc21d36dfe9f0bddaee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:54:27 +0900 Subject: [PATCH 142/322] Send frames to streaming client from replay recorder --- .../Online/Spectator/SpectatorStreamingClient.cs | 13 +++++++++++++ osu.Game/OsuGameBase.cs | 3 +++ osu.Game/Rulesets/UI/ReplayRecorder.cs | 9 +++++++++ 3 files changed, 25 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index a1a4a2774a..c784eb09cd 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -7,7 +7,10 @@ using Microsoft.Extensions.DependencyInjection; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Online.Spectator { @@ -22,6 +25,9 @@ namespace osu.Game.Online.Spectator [Resolved] private APIAccess api { get; set; } + [Resolved] + private IBindable beatmap { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -111,5 +117,12 @@ namespace osu.Game.Online.Spectator public Task EndPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); private Task WatchUser(string userId) => connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + + public void HandleFrame(ReplayFrame frame) + { + if (frame is IConvertibleReplayFrame convertible) + // TODO: don't send a bundle for each individual frame + SendFrames(new FrameDataBundle(new[] { convertible.ToLegacy(beatmap.Value.Beatmap) })); + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9b43d18a88..7364cf04b0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -250,8 +250,11 @@ namespace osu.Game FileStore.Cleanup(); + // add api components to hierarchy. if (API is APIAccess apiAccess) AddInternal(apiAccess); + AddInternal(spectatorStreaming); + AddInternal(RulesetConfigCache); MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index c977639584..3203d1afae 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets.Replays; using osuTK; @@ -60,6 +62,9 @@ namespace osu.Game.Rulesets.UI recordFrame(true); } + [Resolved(canBeNull: true)] + private SpectatorStreamingClient spectatorStreaming { get; set; } + private void recordFrame(bool important) { var last = target.Frames.LastOrDefault(); @@ -72,7 +77,11 @@ namespace osu.Game.Rulesets.UI var frame = HandleFrame(position, pressedActions, last); if (frame != null) + { target.Frames.Add(frame); + + spectatorStreaming?.HandleFrame(frame); + } } protected abstract ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame); From 4788b4a643ac8b1465d200053aa6609ac7c8a679 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 15:03:43 +0900 Subject: [PATCH 143/322] Expose oauth access token via api interface --- osu.Game/Online/API/DummyAPIAccess.cs | 2 ++ osu.Game/Online/API/IAPIProvider.cs | 5 +++++ osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index da22a70bf8..e275676cea 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -20,6 +20,8 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); + public string AccessToken => "token"; + public bool IsLoggedIn => State.Value == APIState.Online; public string ProvidedUsername => LocalUser.Value.Username; diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 256d2ed151..9b7485decd 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -19,6 +19,11 @@ namespace osu.Game.Online.API /// Bindable Activity { get; } + /// + /// Retrieve the OAuth access token. + /// + public string AccessToken { get; } + /// /// Returns whether the local user is logged in. /// diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index c784eb09cd..a9a4987e69 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online.Spectator private readonly IBindable apiState = new Bindable(); [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private IBindable beatmap { get; set; } From 96049c39c9f431f5eaa3709eb1332e83a9bb342b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 15:26:57 +0900 Subject: [PATCH 144/322] Add begin/end session logic --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 3203d1afae..c90b20caeb 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets.Replays; @@ -27,6 +29,12 @@ namespace osu.Game.Rulesets.UI public int RecordFrameRate = 60; + [Resolved(canBeNull: true)] + private SpectatorStreamingClient spectatorStreaming { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + protected ReplayRecorder(Replay target) { this.target = target; @@ -41,6 +49,14 @@ namespace osu.Game.Rulesets.UI base.LoadComplete(); inputManager = GetContainingInputManager(); + + spectatorStreaming?.BeginPlaying(beatmap.Value.BeatmapInfo.ID); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + spectatorStreaming?.EndPlaying(beatmap.Value.BeatmapInfo.ID); } protected override bool OnMouseMove(MouseMoveEvent e) @@ -62,9 +78,6 @@ namespace osu.Game.Rulesets.UI recordFrame(true); } - [Resolved(canBeNull: true)] - private SpectatorStreamingClient spectatorStreaming { get; set; } - private void recordFrame(bool important) { var last = target.Frames.LastOrDefault(); From 2021945a8c87e4f8ec9a556705161f7e42b3c804 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 15:27:04 +0900 Subject: [PATCH 145/322] Add retry/error handling logic --- .../Spectator/SpectatorStreamingClient.cs | 67 ++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index a9a4987e69..2a19a665fc 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -22,6 +22,8 @@ namespace osu.Game.Online.Spectator private readonly IBindable apiState = new Bindable(); + private bool isConnected; + [Resolved] private IAPIProvider api { get; set; } @@ -46,7 +48,7 @@ namespace osu.Game.Online.Spectator break; case APIState.Online: - connect(); + Task.Run(connect); break; } } @@ -57,8 +59,11 @@ namespace osu.Game.Online.Spectator private const string endpoint = "https://spectator.ppy.sh/spectator"; #endif - private void connect() + private async Task connect() { + if (connection != null) + return; + connection = new HubConnectionBuilder() .WithUrl(endpoint, options => { @@ -72,7 +77,33 @@ namespace osu.Game.Online.Spectator connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); - connection.StartAsync(); + connection.Closed += async ex => + { + isConnected = false; + if (ex != null) await tryUntilConnected(); + }; + + await tryUntilConnected(); + + async Task tryUntilConnected() + { + while (api.State.Value == APIState.Online) + { + try + { + // reconnect on any failure + await connection.StartAsync(); + + // success + isConnected = true; + break; + } + catch + { + await Task.Delay(5000); + } + } + } } Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) @@ -106,17 +137,37 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) { - Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First().ToString()}"); + Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First()}"); return Task.CompletedTask; } - public Task BeginPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + public void BeginPlaying(int beatmapId) + { + if (!isConnected) return; - public Task SendFrames(FrameDataBundle data) => connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + } - public Task EndPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + public void SendFrames(FrameDataBundle data) + { + if (!isConnected) return; - private Task WatchUser(string userId) => connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + } + + public void EndPlaying(int beatmapId) + { + if (!isConnected) return; + + connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + } + + public void WatchUser(string userId) + { + if (!isConnected) return; + + connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + } public void HandleFrame(ReplayFrame frame) { From 05697dfe68a58d5a2e2c780a80bb562e31fcd923 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 17:29:38 +0900 Subject: [PATCH 146/322] Add spectator state object support --- osu.Game/Online/Spectator/ISpectatorClient.cs | 8 ++--- osu.Game/Online/Spectator/ISpectatorServer.cs | 9 +++--- osu.Game/Online/Spectator/SpectatorState.cs | 32 +++++++++++++++++++ .../Spectator/SpectatorStreamingClient.cs | 23 ++++++++----- osu.Game/Rulesets/UI/ReplayRecorder.cs | 4 +-- 5 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Online/Spectator/SpectatorState.cs diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index ed762ac1fe..dcff6e6c1c 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -11,15 +11,15 @@ namespace osu.Game.Online.Spectator /// Signals that a user has begun a new play session. /// /// The user. - /// The beatmap the user is playing. - Task UserBeganPlaying(string userId, int beatmapId); + /// The state of gameplay. + Task UserBeganPlaying(string userId, SpectatorState state); /// /// Signals that a user has finished a play session. /// /// The user. - /// The beatmap the user has finished playing. - Task UserFinishedPlaying(string userId, int beatmapId); + /// The state of gameplay. + Task UserFinishedPlaying(string userId, SpectatorState state); /// /// Called when new frames are available for a subscribed user's play session. diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 03ca37d524..018fa6b66b 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -10,8 +10,8 @@ namespace osu.Game.Online.Spectator /// /// Signal the start of a new play session. /// - /// The beatmap currently being played. Eventually this should be replaced with more complete metadata. - Task BeginPlaySession(int beatmapId); + /// The state of gameplay. + Task BeginPlaySession(SpectatorState state); /// /// Send a bundle of frame data for the current play session. @@ -22,15 +22,14 @@ namespace osu.Game.Online.Spectator /// /// Signal the end of a play session. /// - /// The beatmap that was completed. This should be replaced with a play token once that flow is established. - Task EndPlaySession(int beatmapId); + /// The state of gameplay. + Task EndPlaySession(SpectatorState state); /// /// Request spectating data for the specified user. May be called on multiple users and offline users. /// For offline users, a subscription will be created and data will begin streaming on next play. /// /// The user to subscribe to. - /// Task StartWatchingUser(string userId); /// diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs new file mode 100644 index 0000000000..90238bfc38 --- /dev/null +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Online.Spectator +{ + [Serializable] + public class SpectatorState : IEquatable + { + public int? BeatmapID { get; set; } + + [NotNull] + public IEnumerable Mods { get; set; } = Enumerable.Empty(); + + public SpectatorState(int? beatmapId = null, IEnumerable mods = null) + { + BeatmapID = beatmapId; + if (mods != null) + Mods = mods; + } + + public SpectatorState() + { + } + + public bool Equals(SpectatorState other) => this.BeatmapID == other?.BeatmapID && this.Mods.SequenceEqual(other?.Mods); + + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods.SelectMany(m => m.Acronym))}"; + } +} diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 2a19a665fc..d93de3a710 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -30,6 +31,11 @@ namespace osu.Game.Online.Spectator [Resolved] private IBindable beatmap { get; set; } + [Resolved] + private IBindable> mods { get; set; } + + private readonly SpectatorState currentState = new SpectatorState(); + [BackgroundDependencyLoader] private void load() { @@ -73,9 +79,9 @@ namespace osu.Game.Online.Spectator .Build(); // 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.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); connection.Closed += async ex => { @@ -106,7 +112,7 @@ namespace osu.Game.Online.Spectator } } - Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) + Task ISpectatorClient.UserBeganPlaying(string userId, SpectatorState state) { if (connection.ConnectionId != userId) { @@ -123,15 +129,15 @@ namespace osu.Game.Online.Spectator } else { - Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {beatmapId}"); + Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {state}"); } return Task.CompletedTask; } - Task ISpectatorClient.UserFinishedPlaying(string userId, int beatmapId) + Task ISpectatorClient.UserFinishedPlaying(string userId, SpectatorState state) { - Console.WriteLine($"{connection.ConnectionId} Received user finished event {beatmapId}"); + Console.WriteLine($"{connection.ConnectionId} Received user finished event {state}"); return Task.CompletedTask; } @@ -155,11 +161,11 @@ namespace osu.Game.Online.Spectator connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } - public void EndPlaying(int beatmapId) + public void EndPlaying() { if (!isConnected) return; - connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } public void WatchUser(string userId) @@ -171,6 +177,7 @@ namespace osu.Game.Online.Spectator public void HandleFrame(ReplayFrame frame) { + // ReSharper disable once SuspiciousTypeConversion.Global (implemented by rulesets) if (frame is IConvertibleReplayFrame convertible) // TODO: don't send a bundle for each individual frame SendFrames(new FrameDataBundle(new[] { convertible.ToLegacy(beatmap.Value.Beatmap) })); diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index c90b20caeb..a84b4f4ba8 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -50,13 +50,13 @@ namespace osu.Game.Rulesets.UI inputManager = GetContainingInputManager(); - spectatorStreaming?.BeginPlaying(beatmap.Value.BeatmapInfo.ID); + spectatorStreaming?.BeginPlaying(); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - spectatorStreaming?.EndPlaying(beatmap.Value.BeatmapInfo.ID); + spectatorStreaming?.EndPlaying(); } protected override bool OnMouseMove(MouseMoveEvent e) From 0611b30258e1c5c5dcb2c5b346c4658a3773f69a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 17:29:43 +0900 Subject: [PATCH 147/322] Drop webpack --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 11 ++++++++--- osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 2 -- osu.Game/osu.Game.csproj | 3 +-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index d93de3a710..a89cc82535 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -75,7 +76,7 @@ namespace osu.Game.Online.Spectator { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }) - .AddMessagePackProtocol() + .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }) .Build(); // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) @@ -147,11 +148,15 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } - public void BeginPlaying(int beatmapId) + public void BeginPlaying() { if (!isConnected) return; - connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + // transfer state at point of beginning play + currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; + currentState.Mods = mods.Value.ToArray(); + + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } public void SendFrames(FrameDataBundle data) diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index 656fd1814e..c3cffa8699 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.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 MessagePack; using osu.Game.Rulesets.Replays; using osuTK; @@ -9,7 +8,6 @@ namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { - [IgnoreMember] public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); public float? MouseX; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fd010fcc43..ca588b89d9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,9 +21,8 @@ - - + From c834aa605103b95cd9e074b6bd55ac4861694181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 17:38:16 +0900 Subject: [PATCH 148/322] Use APIMod for mod serialization --- osu.Game/Online/API/APIMod.cs | 8 ++++++++ osu.Game/Online/Spectator/SpectatorState.cs | 11 ++++------- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 46a8db31b7..780e5daa16 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -53,5 +53,13 @@ namespace osu.Game.Online.API } public bool Equals(IMod other) => Acronym == other?.Acronym; + + public override string ToString() + { + if (Settings.Count > 0) + return $"{Acronym} ({string.Join(',', Settings.Select(kvp => $"{kvp.Key}:{kvp.Value}"))})"; + + return $"{Acronym}"; + } } } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 90238bfc38..3d9997f006 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using osu.Game.Online.API; using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Spectator @@ -12,21 +13,17 @@ namespace osu.Game.Online.Spectator public int? BeatmapID { get; set; } [NotNull] - public IEnumerable Mods { get; set; } = Enumerable.Empty(); + public IEnumerable Mods { get; set; } = Enumerable.Empty(); - public SpectatorState(int? beatmapId = null, IEnumerable mods = null) + public SpectatorState(int? beatmapId = null, IEnumerable mods = null) { BeatmapID = beatmapId; if (mods != null) Mods = mods; } - public SpectatorState() - { - } - public bool Equals(SpectatorState other) => this.BeatmapID == other?.BeatmapID && this.Mods.SequenceEqual(other?.Mods); - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods.SelectMany(m => m.Acronym))}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)}"; } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index a89cc82535..21259bad5f 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -154,7 +154,7 @@ namespace osu.Game.Online.Spectator // transfer state at point of beginning play currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; - currentState.Mods = mods.Value.ToArray(); + currentState.Mods = mods.Value.Select(m => new APIMod(m)); connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } From 1ab6f41b3ba0127db8ae00609a821fbea36ce26a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 18:10:27 +0900 Subject: [PATCH 149/322] Add basic send and receive test --- .../Visual/Gameplay/TestSceneSpectator.cs | 264 ++++++++++++++++++ .../Spectator/SpectatorStreamingClient.cs | 3 + 2 files changed, 267 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs new file mode 100644 index 0000000000..665df5f9c7 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -0,0 +1,264 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Input.StateChanges; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Spectator; +using osu.Game.Replays; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSpectator : OsuManualInputManagerTestScene + { + protected override bool UseOnlineAPI => true; + + private TestRulesetInputManager playbackManager; + private TestRulesetInputManager recordingManager; + + private Replay replay; + + private TestReplayRecorder recorder; + + [Resolved] + private SpectatorStreamingClient streamingClient { get; set; } + + [SetUp] + public void SetUp() => Schedule(() => + { + replay = new Replay(); + + streamingClient.OnNewFrames += frames => + { + foreach (var legacyFrame in frames.Frames) + { + var frame = new TestReplayFrame(); + frame.FromLegacy(legacyFrame, null, null); + replay.Frames.Add(frame); + } + }; + + Add(new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Recorder = recorder = new TestReplayRecorder + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Sending", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + ReplayInputHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Receiving", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } + } + }); + }); + + [Test] + public void TestBasic() + { + } + + protected override void Update() + { + base.Update(); + playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); + } + + public class TestFramedReplayInputHandler : FramedReplayInputHandler + { + public TestFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override void CollectPendingInputs(List inputs) + { + inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); + inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); + } + } + + public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); + + private readonly Box box; + + public TestInputConsumer() + { + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } + } + + public class TestRulesetInputManager : RulesetInputManager + { + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } + } + + public class TestReplayFrame : ReplayFrame, IConvertibleReplayFrame + { + public Vector2 Position; + + public List Actions = new List(); + + public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) + : base(time) + { + Position = position; + Actions.AddRange(actions); + } + + public TestReplayFrame() + { + } + + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + { + Position = currentFrame.Position; + Time = currentFrame.Time; + if (currentFrame.MouseLeft) + Actions.Add(TestAction.Down); + } + + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(TestAction.Down)) + state |= ReplayButtonState.Left1; + + return new LegacyReplayFrame(Time, Position.X, Position.Y, state); + } + } + + public enum TestAction + { + Down, + } + + internal class TestReplayRecorder : ReplayRecorder + { + public TestReplayRecorder() + : base(new Replay()) + { + } + + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) + { + return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + } + } + } +} diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 21259bad5f..608123fbab 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -37,6 +37,8 @@ namespace osu.Game.Online.Spectator private readonly SpectatorState currentState = new SpectatorState(); + public event Action OnNewFrames; + [BackgroundDependencyLoader] private void load() { @@ -145,6 +147,7 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) { Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First()}"); + OnNewFrames?.Invoke(data); return Task.CompletedTask; } From 34e889e66e3bfeabd22ea5eb6550b5e338c15bce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 18:37:19 +0900 Subject: [PATCH 150/322] Don't watch every user in normal gameplay (but allow so in test) --- .../Visual/Gameplay/TestSceneSpectator.cs | 20 ++++- osu.Game/Online/Spectator/ISpectatorClient.cs | 6 +- osu.Game/Online/Spectator/ISpectatorServer.cs | 4 +- osu.Game/Online/Spectator/SpectatorState.cs | 1 - .../Spectator/SpectatorStreamingClient.cs | 85 ++++++++++++------- 5 files changed, 78 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 665df5f9c7..2ec82ad5fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.Specialized; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -34,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private TestReplayRecorder recorder; + private IBindableList users; [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -44,7 +46,19 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - streamingClient.OnNewFrames += frames => + users = streamingClient.PlayingUsers.GetBoundCopy(); + users.BindCollectionChanged((obj, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (int user in args.NewItems) + streamingClient.WatchUser(user); + break; + } + }, true); + + streamingClient.OnNewFrames += (userId, frames) => { foreach (var legacyFrame in frames.Frames) { @@ -63,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = recorder = new TestReplayRecorder + Recorder = new TestReplayRecorder { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index dcff6e6c1c..18c9d61561 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -12,20 +12,20 @@ namespace osu.Game.Online.Spectator /// /// The user. /// The state of gameplay. - Task UserBeganPlaying(string userId, SpectatorState state); + Task UserBeganPlaying(int userId, SpectatorState state); /// /// Signals that a user has finished a play session. /// /// The user. /// The state of gameplay. - Task UserFinishedPlaying(string userId, SpectatorState state); + Task UserFinishedPlaying(int userId, SpectatorState state); /// /// Called when new frames are available for a subscribed user's play session. /// /// The user. /// The frame data. - Task UserSentFrames(string userId, FrameDataBundle data); + Task UserSentFrames(int userId, FrameDataBundle data); } } diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 018fa6b66b..99893e385c 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -30,12 +30,12 @@ namespace osu.Game.Online.Spectator /// For offline users, a subscription will be created and data will begin streaming on next play. /// /// The user to subscribe to. - Task StartWatchingUser(string userId); + Task StartWatchingUser(int userId); /// /// Stop requesting spectating data for the specified user. Unsubscribes from receiving further data. /// /// The user to unsubscribe from. - Task EndWatchingUser(string userId); + Task EndWatchingUser(int userId); } } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 3d9997f006..6b2b8b8cb2 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using osu.Game.Online.API; -using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Spectator { diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 608123fbab..2665243e4c 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -20,7 +21,11 @@ namespace osu.Game.Online.Spectator { private HubConnection connection; - private readonly List watchingUsers = new List(); + private readonly List watchingUsers = new List(); + + public IBindableList PlayingUsers => playingUsers; + + private readonly BindableList playingUsers = new BindableList(); private readonly IBindable apiState = new Bindable(); @@ -37,7 +42,12 @@ namespace osu.Game.Online.Spectator private readonly SpectatorState currentState = new SpectatorState(); - public event Action OnNewFrames; + private bool isPlaying; + + /// + /// Called whenever new frames arrive from the server. + /// + public event Action OnNewFrames; [BackgroundDependencyLoader] private void load() @@ -82,13 +92,15 @@ namespace osu.Game.Online.Spectator .Build(); // 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); + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); connection.Closed += async ex => { isConnected = false; + playingUsers.Clear(); + if (ex != null) await tryUntilConnected(); }; @@ -105,6 +117,17 @@ namespace osu.Game.Online.Spectator // success isConnected = true; + + // resubscribe to watched users + var users = watchingUsers.ToArray(); + watchingUsers.Clear(); + foreach (var userId in users) + WatchUser(userId); + + // re-send state in case it wasn't received + if (isPlaying) + beginPlaying(); + break; } catch @@ -115,39 +138,23 @@ namespace osu.Game.Online.Spectator } } - Task ISpectatorClient.UserBeganPlaying(string userId, SpectatorState state) + Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { - if (connection.ConnectionId != userId) - { - if (watchingUsers.Contains(userId)) - { - Console.WriteLine($"{connection.ConnectionId} received began playing for already watched user {userId}"); - } - else - { - Console.WriteLine($"{connection.ConnectionId} requesting watch other user {userId}"); - WatchUser(userId); - watchingUsers.Add(userId); - } - } - else - { - Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {state}"); - } + if (!playingUsers.Contains(userId)) + playingUsers.Add(userId); return Task.CompletedTask; } - Task ISpectatorClient.UserFinishedPlaying(string userId, SpectatorState state) + Task ISpectatorClient.UserFinishedPlaying(int userId, SpectatorState state) { - Console.WriteLine($"{connection.ConnectionId} Received user finished event {state}"); + playingUsers.Remove(userId); return Task.CompletedTask; } - Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) + Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data) { - Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First()}"); - OnNewFrames?.Invoke(data); + OnNewFrames?.Invoke(userId, data); return Task.CompletedTask; } @@ -155,10 +162,22 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; + if (isPlaying) + throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); + + isPlaying = true; + // transfer state at point of beginning play currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; currentState.Mods = mods.Value.Select(m => new APIMod(m)); + beginPlaying(); + } + + private void beginPlaying() + { + Debug.Assert(isPlaying); + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } @@ -173,13 +192,21 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; + if (!isPlaying) + throw new InvalidOperationException($"Cannot invoke {nameof(EndPlaying)} when not playing"); + + isPlaying = false; connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } - public void WatchUser(string userId) + public void WatchUser(int userId) { if (!isConnected) return; + if (watchingUsers.Contains(userId)) + return; + + watchingUsers.Add(userId); connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } From d659b7739d4c78ff8926565145c1952fdb91b76f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:16:34 +0900 Subject: [PATCH 151/322] Correctly stop watching users that leave --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 2ec82ad5fb..be3241c784 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -55,6 +55,11 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (int user in args.NewItems) streamingClient.WatchUser(user); break; + + case NotifyCollectionChangedAction.Remove: + foreach (int user in args.OldItems) + streamingClient.StopWatchingUser(user); + break; } }, true); From 823d717a7d986e5551b50d87f1d3abefdffb560b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:17:10 +0900 Subject: [PATCH 152/322] Reduce the serialised size of LegacyReplayFrame --- osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index c3cffa8699..74bacae9e1 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.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 Newtonsoft.Json; using osu.Game.Rulesets.Replays; using osuTK; @@ -8,17 +9,28 @@ namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { + [JsonIgnore] public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); public float? MouseX; public float? MouseY; + [JsonIgnore] public bool MouseLeft => MouseLeft1 || MouseLeft2; + + [JsonIgnore] public bool MouseRight => MouseRight1 || MouseRight2; + [JsonIgnore] public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1); + + [JsonIgnore] public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1); + + [JsonIgnore] public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2); + + [JsonIgnore] public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2); public ReplayButtonState ButtonState; From ee2513bf4b7fc17192dc584c21f1f1a911c59971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:17:19 +0900 Subject: [PATCH 153/322] Add batch sending --- .../Spectator/SpectatorStreamingClient.cs | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 2665243e4c..9ebb84c007 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -185,7 +186,7 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; - connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + lastSend = connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } public void EndPlaying() @@ -201,21 +202,64 @@ namespace osu.Game.Online.Spectator public void WatchUser(int userId) { - if (!isConnected) return; - if (watchingUsers.Contains(userId)) return; watchingUsers.Add(userId); + + if (!isConnected) return; + connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } + public void StopWatchingUser(int userId) + { + watchingUsers.Remove(userId); + + if (!isConnected) return; + + connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); + } + + private readonly Queue pendingFrames = new Queue(); + + private double lastSendTime; + + private Task lastSend; + + private const double time_between_sends = 200; + + private const int max_pending_frames = 30; + + protected override void Update() + { + base.Update(); + + if (pendingFrames.Count > 0 && Time.Current - lastSendTime > time_between_sends) + purgePendingFrames(); + } + public void HandleFrame(ReplayFrame frame) { - // ReSharper disable once SuspiciousTypeConversion.Global (implemented by rulesets) if (frame is IConvertibleReplayFrame convertible) - // TODO: don't send a bundle for each individual frame - SendFrames(new FrameDataBundle(new[] { convertible.ToLegacy(beatmap.Value.Beatmap) })); + pendingFrames.Enqueue(convertible.ToLegacy(beatmap.Value.Beatmap)); + + if (pendingFrames.Count > max_pending_frames) + purgePendingFrames(); + } + + private void purgePendingFrames() + { + if (lastSend?.IsCompleted == false) + return; + + var frames = pendingFrames.ToArray(); + + pendingFrames.Clear(); + + SendFrames(new FrameDataBundle(frames)); + + lastSendTime = Time.Current; } } } From 04f46bc1f84739780214d468f1d79a298ffd4ee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:24:32 +0900 Subject: [PATCH 154/322] Clean up usings --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 71cd39953c..d464eee7c5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -13,9 +13,7 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Spectator; using osu.Game.Replays; -using osu.Game.Replays.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; From 147d502da13bb303af11cf53395cd53ed09f4e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:30:07 +0900 Subject: [PATCH 155/322] Fix initial play state not being kept locally if not connected --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 9ebb84c007..6737625818 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -161,8 +161,6 @@ namespace osu.Game.Online.Spectator public void BeginPlaying() { - if (!isConnected) return; - if (isPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); @@ -179,6 +177,8 @@ namespace osu.Game.Online.Spectator { Debug.Assert(isPlaying); + if (!isConnected) return; + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } From 51ae93d484f31fd586fddee4095657caaf0924ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:31:56 +0900 Subject: [PATCH 156/322] Revert unnecessary file changes --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index d464eee7c5..bc1c10e59d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -266,9 +266,7 @@ namespace osu.Game.Tests.Visual.Gameplay } protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) - { - return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); - } + => new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } } From 9f2f8d8cc778df600182d50defdf28ae5106f0ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:41:10 +0900 Subject: [PATCH 157/322] Fix missing licence headers --- osu.Game/Online/Spectator/FrameDataBundle.cs | 3 +++ osu.Game/Online/Spectator/ISpectatorClient.cs | 3 +++ osu.Game/Online/Spectator/ISpectatorServer.cs | 3 +++ osu.Game/Online/Spectator/SpectatorState.cs | 5 ++++- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 3 +++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs index 67f2688289..5281e61f9c 100644 --- a/osu.Game/Online/Spectator/FrameDataBundle.cs +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -1,3 +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 osu.Game.Replays.Legacy; diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index 18c9d61561..3acc9b2282 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -1,3 +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.Threading.Tasks; namespace osu.Game.Online.Spectator diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 99893e385c..af0196862a 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -1,3 +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.Threading.Tasks; namespace osu.Game.Online.Spectator diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 6b2b8b8cb2..48fad4b3b2 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -1,3 +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.Diagnostics.CodeAnalysis; @@ -21,7 +24,7 @@ namespace osu.Game.Online.Spectator Mods = mods; } - public bool Equals(SpectatorState other) => this.BeatmapID == other?.BeatmapID && this.Mods.SequenceEqual(other?.Mods); + public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods); public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)}"; } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 6737625818..006f75c1d2 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -1,3 +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.Diagnostics; From 54d666604bc0daa207aa1c923715a391b22badb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 22:56:23 +0900 Subject: [PATCH 158/322] Fix incorrect order of flag settings --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 006f75c1d2..2fc1431702 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -194,12 +194,13 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - if (!isConnected) return; - if (!isPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(EndPlaying)} when not playing"); isPlaying = false; + + if (!isConnected) return; + connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } From 2871001cc294da01f2db8e8c35d84a86ab1503ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 22 Oct 2020 18:21:28 +0200 Subject: [PATCH 159/322] Add BackgroundSource.Seasonal --- osu.Game/Configuration/BackgroundSource.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/BackgroundSource.cs b/osu.Game/Configuration/BackgroundSource.cs index 5726e96eb1..beef9ef1de 100644 --- a/osu.Game/Configuration/BackgroundSource.cs +++ b/osu.Game/Configuration/BackgroundSource.cs @@ -6,6 +6,7 @@ namespace osu.Game.Configuration public enum BackgroundSource { Skin, - Beatmap + Beatmap, + Seasonal } } From cdb2d23578e6de7ca266a23a51b5fac2ed15b8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 22 Oct 2020 18:23:03 +0200 Subject: [PATCH 160/322] Set BackgroundSource.Seasonal as default setting --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d601c0cb9..5c5af701bb 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -130,7 +130,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IntroSequence, IntroSequence.Triangles); - Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); + Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Seasonal); } public OsuConfigManager(Storage storage) From 09d49aa0f7b2d953370e02897ef382bd3ea04f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 22 Oct 2020 18:25:01 +0200 Subject: [PATCH 161/322] Add GetSeasonalBackgroundsRequest --- .../Requests/GetSeasonalBackgroundsRequest.cs | 12 +++++++++++ .../Responses/APISeasonalBackgrounds.cs | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 osu.Game/Online/API/Requests/GetSeasonalBackgroundsRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs diff --git a/osu.Game/Online/API/Requests/GetSeasonalBackgroundsRequest.cs b/osu.Game/Online/API/Requests/GetSeasonalBackgroundsRequest.cs new file mode 100644 index 0000000000..941b47244a --- /dev/null +++ b/osu.Game/Online/API/Requests/GetSeasonalBackgroundsRequest.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetSeasonalBackgroundsRequest : APIRequest + { + protected override string Target => @"seasonal-backgrounds"; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs b/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs new file mode 100644 index 0000000000..6996ac4d9b --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.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 System.Collections.Generic; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APISeasonalBackgrounds + { + [JsonProperty("backgrounds")] + public List Backgrounds { get; set; } + } + + public class APISeasonalBackground + { + [JsonProperty("url")] + public string Url { get; set; } + } +} From f11bcfcb8f301b8d19e465176a7b66ca70f88858 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 10:03:33 +0900 Subject: [PATCH 162/322] Remove unnecessary public specification in interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/API/IAPIProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 9b7485decd..d10cb4b6d2 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online.API /// /// Retrieve the OAuth access token. /// - public string AccessToken { get; } + string AccessToken { get; } /// /// Returns whether the local user is logged in. From e99cf369fac8e35b16a2b9458651f847cb1905f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 13:33:23 +0900 Subject: [PATCH 163/322] Don't worry about EndPlaying being invoked when not playing --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 2fc1431702..1ca0a378bb 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -194,9 +194,6 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - if (!isPlaying) - throw new InvalidOperationException($"Cannot invoke {nameof(EndPlaying)} when not playing"); - isPlaying = false; if (!isConnected) return; From 55f1b05dbf6c74e389cfc6af2133ed03bd7e2da0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 14:47:08 +0900 Subject: [PATCH 164/322] Fix test failures due to recorder not stopping in time --- .../Visual/Gameplay/TestSceneReplayRecorder.cs | 6 ++++++ osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index bc1c10e59d..e964d2a40e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -166,6 +166,12 @@ namespace osu.Game.Tests.Visual.Gameplay playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); } + [TearDownSteps] + public void TearDown() + { + AddStep("stop recorder", () => recorder.Expire()); + } + public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index be3241c784..f8b5d385a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; @@ -38,6 +39,8 @@ namespace osu.Game.Tests.Visual.Gameplay private IBindableList users; + private TestReplayRecorder recorder; + [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -82,7 +85,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder + Recorder = recorder = new TestReplayRecorder { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, @@ -153,6 +156,12 @@ namespace osu.Game.Tests.Visual.Gameplay playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); } + [TearDownSteps] + public void TearDown() + { + AddStep("stop recorder", () => recorder.Expire()); + } + public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) From 4fca7675b07fbd9c9784560ec22479cc986c0223 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 14:47:21 +0900 Subject: [PATCH 165/322] Don't send spectate data when an autoplay mod is active --- osu.Game/Screens/Play/Player.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9ee0b8a54f..6b2d2f40d0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -152,7 +152,9 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - PrepareReplay(); + // replays should never be recorded or played back when autoplay is enabled + if (!Mods.Value.Any(m => m is ModAutoplay)) + PrepareReplay(); } private Replay recordingReplay; From 9141f48b047c0a59fa49c0dfadfef578900753f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 14:57:27 +0900 Subject: [PATCH 166/322] Remove beatmap-based ctor to promote single flow --- .../Expanded/ExpandedPanelMiddleContent.cs | 5 +++-- .../Ranking/Expanded/StarRatingDisplay.cs | 22 ++++--------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5aac449adb..30747438c3 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -51,7 +52,7 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load() + private void load(BeatmapDifficultyManager beatmapDifficultyManager) { var beatmap = score.Beatmap; var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; @@ -138,7 +139,7 @@ namespace osu.Game.Screens.Ranking.Expanded Spacing = new Vector2(5, 0), Children = new Drawable[] { - new StarRatingDisplay(beatmap) + new StarRatingDisplay(beatmapDifficultyManager.GetDifficulty(beatmap, score.Ruleset, score.Mods)) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 402ab99908..ffb12d474b 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -22,18 +22,7 @@ namespace osu.Game.Screens.Ranking.Expanded /// public class StarRatingDisplay : CompositeDrawable { - private readonly BeatmapInfo beatmap; - - private StarDifficulty? difficulty; - - /// - /// Creates a new . - /// - /// The to display the star difficulty of. - public StarRatingDisplay(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - } + private readonly StarDifficulty difficulty; /// /// Creates a new using an already computed . @@ -49,17 +38,14 @@ namespace osu.Game.Screens.Ranking.Expanded { AutoSizeAxes = Axes.Both; - if (!difficulty.HasValue) - difficulty = difficultyManager.GetDifficulty(beatmap); - - var starRatingParts = difficulty.Value.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - ColourInfo backgroundColour = difficulty.Value.DifficultyRating == DifficultyRating.ExpertPlus + ColourInfo backgroundColour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.Value.DifficultyRating); + : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); InternalChildren = new Drawable[] { From 9404096a28c49a2c9370d6dd2d07a893d86f82df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 15:06:00 +0900 Subject: [PATCH 167/322] Update tests to match new constructor --- .../Visual/Ranking/TestSceneStarRatingDisplay.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index d12f32e470..d0067c3396 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -18,13 +18,13 @@ namespace osu.Game.Tests.Visual.Ranking Origin = Anchor.Centre, Children = new Drawable[] { - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 1.23 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 2.34 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 3.45 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 4.56 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 5.67 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 6.78 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 10.11 }), + new StarRatingDisplay(new StarDifficulty(1.23, 0)), + new StarRatingDisplay(new StarDifficulty(2.34, 0)), + new StarRatingDisplay(new StarDifficulty(3.45, 0)), + new StarRatingDisplay(new StarDifficulty(4.56, 0)), + new StarRatingDisplay(new StarDifficulty(5.67, 0)), + new StarRatingDisplay(new StarDifficulty(6.78, 0)), + new StarRatingDisplay(new StarDifficulty(10.11, 0)), } }; } From 1b84402b966744babc95e20790f83fa3061b9f8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 15:33:38 +0900 Subject: [PATCH 168/322] Centralise and share logic for storyboard frame lookup method --- .../Drawables/DrawableStoryboardAnimation.cs | 19 +++++-------------- .../Drawables/DrawableStoryboardSprite.cs | 16 +++++----------- osu.Game/Storyboards/Storyboard.cs | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 8382f91d1f..97de239e4a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Skinning; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -117,18 +113,13 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) + private void load(TextureStore textureStore, Storyboard storyboard) { - for (var frame = 0; frame < Animation.FrameCount; frame++) + for (int frame = 0; frame < Animation.FrameCount; frame++) { - var framePath = Animation.Path.Replace(".", frame + "."); + string framePath = Animation.Path.Replace(".", frame + "."); - var storyboardPath = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var frameSprite = storyboard.UseSkinSprites && storyboardPath == null - ? (Drawable)new SkinnableSprite(framePath) - : new Sprite { Texture = textureStore.Get(storyboardPath) }; - - AddFrame(frameSprite, Animation.FrameDelay); + AddFrame(storyboard.CreateSpriteFromResourcePath(framePath, textureStore), Animation.FrameDelay); } Animation.ApplyTransforms(this); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 9599375c76..1adbe688e7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Skinning; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -116,14 +112,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) + private void load(TextureStore textureStore, Storyboard storyboard) { - var storyboardPath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var sprite = storyboard.UseSkinSprites && storyboardPath == null - ? (Drawable)new SkinnableSprite(Sprite.Path) - : new Sprite { Texture = textureStore.Get(storyboardPath) }; + var drawable = storyboard.CreateSpriteFromResourcePath(Sprite.Path, textureStore); - InternalChild = sprite.With(s => s.Anchor = s.Origin = Anchor.Centre); + if (drawable != null) + InternalChild = drawable.With(s => s.Anchor = s.Origin = Anchor.Centre); Sprite.ApplyTransforms(this); } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index daafdf015d..e0d18eab00 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -1,9 +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.Collections.Generic; using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards @@ -69,5 +74,19 @@ namespace osu.Game.Storyboards drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } + + public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) + { + Drawable drawable = null; + var storyboardPath = BeatmapInfo.BeatmapSet?.Files?.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + if (storyboardPath != null) + drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; + // if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy. + else if (UseSkinSprites) + drawable = new SkinnableSprite(path); + + return drawable; + } } } From 4f746792fba1f427357675b0c054ff84b2bd95b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 15:46:24 +0900 Subject: [PATCH 169/322] Fix regression causing storyboard sprites to have incorrect origin support --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 1adbe688e7..7b1a6d54da 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -109,6 +109,8 @@ namespace osu.Game.Storyboards.Drawables LifetimeStart = sprite.StartTime; LifetimeEnd = sprite.EndTime; + + AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -117,7 +119,7 @@ namespace osu.Game.Storyboards.Drawables var drawable = storyboard.CreateSpriteFromResourcePath(Sprite.Path, textureStore); if (drawable != null) - InternalChild = drawable.With(s => s.Anchor = s.Origin = Anchor.Centre); + InternalChild = drawable; Sprite.ApplyTransforms(this); } From e20a98640199bce08ba1f445ca283027f8fe9282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 17:24:19 +0900 Subject: [PATCH 170/322] Add ruleset to state --- osu.Game/Online/Spectator/SpectatorState.cs | 13 ++++--------- .../Online/Spectator/SpectatorStreamingClient.cs | 5 +++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 48fad4b3b2..101ce3d5d5 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -14,18 +14,13 @@ namespace osu.Game.Online.Spectator { public int? BeatmapID { get; set; } + public int? RulesetID { get; set; } + [NotNull] public IEnumerable Mods { get; set; } = Enumerable.Empty(); - public SpectatorState(int? beatmapId = null, IEnumerable mods = null) - { - BeatmapID = beatmapId; - if (mods != null) - Mods = mods; - } + public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods) && RulesetID == other?.RulesetID; - public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods); - - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}"; } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 1ca0a378bb..43bc8ff71b 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -41,6 +42,9 @@ namespace osu.Game.Online.Spectator [Resolved] private IBindable beatmap { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + [Resolved] private IBindable> mods { get; set; } @@ -171,6 +175,7 @@ namespace osu.Game.Online.Spectator // transfer state at point of beginning play currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; + currentState.RulesetID = ruleset.Value.ID; currentState.Mods = mods.Value.Select(m => new APIMod(m)); beginPlaying(); From c1d03a5baceb23be78f0d55458a9085ad5b652ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 23 Oct 2020 13:40:13 +0200 Subject: [PATCH 171/322] Add SeasonalBackgroundLoader and SeasonalBackground --- .../Backgrounds/SeasonalBackgroundLoader.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs new file mode 100644 index 0000000000..af81b25cee --- /dev/null +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -0,0 +1,68 @@ +// 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 JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Graphics.Backgrounds +{ + [LongRunningLoad] + public class SeasonalBackgroundLoader : Component + { + private List backgrounds = new List(); + private int current; + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + var request = new GetSeasonalBackgroundsRequest(); + request.Success += response => + { + backgrounds = response.Backgrounds ?? backgrounds; + current = RNG.Next(0, backgrounds.Count); + }; + + api.PerformAsync(request); + } + + public SeasonalBackground LoadBackground(string fallbackTextureName) + { + string url = null; + + if (backgrounds.Any()) + { + current = (current + 1) % backgrounds.Count; + url = backgrounds[current].Url; + } + + return new SeasonalBackground(url, fallbackTextureName); + } + } + + [LongRunningLoad] + public class SeasonalBackground : Background + { + private readonly string url; + private readonly string fallbackTextureName; + + public SeasonalBackground([CanBeNull] string url, string fallbackTextureName = @"Backgrounds/bg1") + { + this.url = url; + this.fallbackTextureName = fallbackTextureName; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + Sprite.Texture = textures.Get(url) ?? textures.Get(fallbackTextureName); + } + } +} From 81ebcd879668eb13cb28aa11bf4edfb8afb0fb99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 23 Oct 2020 13:41:00 +0200 Subject: [PATCH 172/322] Load SeasonalBackgroundLoader asynchronously --- .../Backgrounds/BackgroundScreenDefault.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index ef41c5be3d..ec91dcc99f 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Backgrounds private Bindable skin; private Bindable mode; private Bindable introSequence; + private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); [Resolved] private IBindable beatmap { get; set; } @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Backgrounds currentDisplay = RNG.Next(0, background_count); - display(createBackground()); + LoadComponentAsync(seasonalBackgroundLoader, _ => LoadComponentAsync(createBackground(), display)); } private void display(Background newBackground) @@ -90,6 +91,10 @@ namespace osu.Game.Screens.Backgrounds { switch (mode.Value) { + case BackgroundSource.Seasonal: + newBackground = seasonalBackgroundLoader.LoadBackground(backgroundName); + break; + case BackgroundSource.Beatmap: newBackground = new BeatmapBackground(beatmap.Value, backgroundName); break; @@ -100,7 +105,18 @@ namespace osu.Game.Screens.Backgrounds } } else - newBackground = new Background(backgroundName); + { + switch (mode.Value) + { + case BackgroundSource.Seasonal: + newBackground = seasonalBackgroundLoader.LoadBackground(backgroundName); + break; + + default: + newBackground = new Background(backgroundName); + break; + } + } newBackground.Depth = currentDisplay; From ae9e60560bc3e00efb4b56692afd387d2a6e1564 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 23 Oct 2020 14:11:29 +0200 Subject: [PATCH 173/322] Fixed gameplay flags being bigger and changed values to make more sense --- osu.Game.Tournament/Components/DrawableTeamFlag.cs | 2 +- osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs | 1 + osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 2 +- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index a2e0bf83be..75991a1ab8 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tournament.Components { if (team == null) return; - Size = new Vector2(70, 47); + Size = new Vector2(75, 50); Masking = true; CornerRadius = 5; Child = flagSprite = new Sprite diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 44921f06ad..4ba86dcefc 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components var anchor = flip ? Anchor.TopLeft : Anchor.TopRight; Flag.RelativeSizeAxes = Axes.None; + Flag.Scale = new Vector2(0.8f); Flag.Origin = anchor; Flag.Anchor = anchor; diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 32830713f6..55fc80dba2 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -288,7 +288,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro AutoSizeAxes = Axes.Both; Flag.RelativeSizeAxes = Axes.None; - Flag.Scale = new Vector2(1.4f); + Flag.Scale = new Vector2(1.2f); InternalChild = new FillFlowContainer { diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 3972c590ea..7ca262a2e8 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(-300, 10), - Scale = new Vector2(2.2f) + Scale = new Vector2(2f) }, new FillFlowContainer { From c24a29d1acdde0228ffdd24f7726e0a0100f9e16 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 23 Oct 2020 14:23:08 +0200 Subject: [PATCH 174/322] Update flag scale of drawablematchteam --- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 030ccb5cb3..ba577888d8 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components this.losers = losers; Size = new Vector2(150, 40); - Flag.Scale = new Vector2(0.6f); + Flag.Scale = new Vector2(0.55f); Flag.Anchor = Flag.Origin = Anchor.CentreLeft; AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; From 73174961f02f01123f1c3cef900bf8a9475d8e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 21:22:47 +0200 Subject: [PATCH 175/322] Rework animation sequence for readability --- osu.Game/Screens/Play/PlayerLoader.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fae0bfb295..be3bad1517 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -331,18 +331,11 @@ namespace osu.Game.Screens.Play { const double epilepsy_display_length = 3000; - pushSequence.Schedule(() => - { - epilepsyWarning.State.Value = Visibility.Visible; - - this.Delay(epilepsy_display_length).Schedule(() => - { - epilepsyWarning.Hide(); - epilepsyWarning.Expire(); - }); - }); - - pushSequence.Delay(epilepsy_display_length); + pushSequence + .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) + .Delay(epilepsy_display_length) + .Schedule(() => epilepsyWarning.Hide()) + .Delay(EpilepsyWarning.FADE_DURATION); } pushSequence.Schedule(() => From e101ba5cba4eb63ef287a60d7d1bd121893f741d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Oct 2020 22:58:13 +0200 Subject: [PATCH 176/322] Move volume manipulations to player loader --- osu.Game/Screens/Play/EpilepsyWarning.cs | 18 ------------------ osu.Game/Screens/Play/PlayerLoader.cs | 23 +++++++++++++++++++++-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index e3cf0cd227..6121a0c2a3 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,11 +16,6 @@ namespace osu.Game.Screens.Play { public class EpilepsyWarning : VisibilityContainer { - public const double FADE_DURATION = 500; - - private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); - - private Track track; public EpilepsyWarning() { @@ -77,26 +70,15 @@ namespace osu.Game.Screens.Play } } }; - - track = beatmap.Value.Track; - track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); } protected override void PopIn() { - this.TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, FADE_DURATION); - DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint); this.FadeIn(FADE_DURATION, Easing.OutQuint); } protected override void PopOut() => this.FadeOut(FADE_DURATION); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); - } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index be3bad1517..fe774527b8 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -104,6 +104,9 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } + [Resolved] + private MusicController musicController { get; set; } + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -332,9 +335,17 @@ namespace osu.Game.Screens.Play const double epilepsy_display_length = 3000; pushSequence - .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) + .Schedule(() => + { + musicController.CurrentTrack.VolumeTo(0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); + epilepsyWarning.State.Value = Visibility.Visible; + }) .Delay(epilepsy_display_length) - .Schedule(() => epilepsyWarning.Hide()) + .Schedule(() => + { + epilepsyWarning.Hide(); + epilepsyWarning.Expire(); + }) .Delay(EpilepsyWarning.FADE_DURATION); } @@ -348,6 +359,10 @@ namespace osu.Game.Screens.Play // Note that this may change if the player we load requested a re-run. ValidForResume = false; + // restore full volume immediately - there's a usually a period of silence at start of gameplay anyway. + // note that this is delayed slightly to avoid volume spikes just before push. + musicController.CurrentTrack.Delay(50).VolumeTo(1); + if (player.LoadedBeatmapSuccessfully) this.Push(player); else @@ -363,6 +378,10 @@ namespace osu.Game.Screens.Play private void cancelLoad() { + // in case the epilepsy warning is being displayed, restore full volume. + if (epilepsyWarning?.IsAlive == true) + musicController.CurrentTrack.VolumeTo(1, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); + scheduledPushPlayer?.Cancel(); scheduledPushPlayer = null; } From 85e14f3f0c8b3e194cb51057e4e9b2970dda7a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Oct 2020 22:58:43 +0200 Subject: [PATCH 177/322] Shorten fade duration to make fade out snappier --- osu.Game/Screens/Play/EpilepsyWarning.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 6121a0c2a3..dc42427fbf 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -16,6 +16,7 @@ namespace osu.Game.Screens.Play { public class EpilepsyWarning : VisibilityContainer { + public const double FADE_DURATION = 250; public EpilepsyWarning() { From 8b04cd2cb0a79a81432bff9f488d12300240ebac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Oct 2020 20:28:24 +0900 Subject: [PATCH 178/322] Fix a potential null reference when loading carousel difficulties --- 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 703b91c517..93f95e76cc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Select.Carousel LoadComponentAsync(beatmapContainer, loaded => { // make sure the pooled target hasn't changed. - if (carouselBeatmapSet != Item) + if (beatmapContainer != loaded) return; Content.Child = loaded; From 0542a45c43a14a16eec8a99570c070e642104fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Oct 2020 12:33:35 +0100 Subject: [PATCH 179/322] Change to manual adjustment add/remove --- osu.Game/Screens/Play/PlayerLoader.cs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fe774527b8..42074ac241 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Play private bool backgroundBrightnessReduction; + private readonly BindableDouble volumeAdjustment = new BindableDouble(1); + protected bool BackgroundBrightnessReduction { set @@ -104,9 +106,6 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } - [Resolved] - private MusicController musicController { get; set; } - public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -172,6 +171,7 @@ namespace osu.Game.Screens.Play if (epilepsyWarning != null) epilepsyWarning.DimmableBackground = Background; + Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); content.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); @@ -200,6 +200,11 @@ namespace osu.Game.Screens.Play cancelLoad(); BackgroundBrightnessReduction = false; + + // we're moving to player, so a period of silence is upcoming. + // stop the track before removing adjustment to avoid a volume spike. + Beatmap.Value.Track.Stop(); + Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); } public override bool OnExiting(IScreen next) @@ -211,6 +216,7 @@ namespace osu.Game.Screens.Play Background.EnableUserDim.Value = false; BackgroundBrightnessReduction = false; + Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); return base.OnExiting(next); } @@ -335,11 +341,8 @@ namespace osu.Game.Screens.Play const double epilepsy_display_length = 3000; pushSequence - .Schedule(() => - { - musicController.CurrentTrack.VolumeTo(0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); - epilepsyWarning.State.Value = Visibility.Visible; - }) + .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) + .TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint) .Delay(epilepsy_display_length) .Schedule(() => { @@ -359,10 +362,6 @@ namespace osu.Game.Screens.Play // Note that this may change if the player we load requested a re-run. ValidForResume = false; - // restore full volume immediately - there's a usually a period of silence at start of gameplay anyway. - // note that this is delayed slightly to avoid volume spikes just before push. - musicController.CurrentTrack.Delay(50).VolumeTo(1); - if (player.LoadedBeatmapSuccessfully) this.Push(player); else @@ -378,10 +377,6 @@ namespace osu.Game.Screens.Play private void cancelLoad() { - // in case the epilepsy warning is being displayed, restore full volume. - if (epilepsyWarning?.IsAlive == true) - musicController.CurrentTrack.VolumeTo(1, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); - scheduledPushPlayer?.Cancel(); scheduledPushPlayer = null; } From 0a23e994e2de7a23927928e4a051d6372a7c8dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Oct 2020 23:24:14 +0100 Subject: [PATCH 180/322] Hide sliderend & repeat circles in traceable mod --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index d7582f3196..e1d197fb1d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using System.Collections.Generic; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -49,9 +50,16 @@ namespace osu.Game.Rulesets.Osu.Mods { case DrawableHitCircle circle: // we only want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - circle.CirclePiece.Hide(); + applyCirclePieceState(circle, circle.CirclePiece); + break; + case DrawableSliderTail sliderTail: + applyCirclePieceState(sliderTail); + break; + + case DrawableSliderRepeat sliderRepeat: + // show only the repeat arrow + applyCirclePieceState(sliderRepeat, sliderRepeat.CirclePiece); break; case DrawableSlider slider: @@ -61,6 +69,13 @@ namespace osu.Game.Rulesets.Osu.Mods } } + private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null) + { + var h = hitObject.HitObject; + using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + (hitCircle ?? hitObject).Hide(); + } + private void applySliderState(DrawableSlider slider) { ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); From 5ef1b5dcb521a7d75f4f8dd6e7e82c934a3c195c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Oct 2020 23:55:22 +0100 Subject: [PATCH 181/322] Remove unused locals --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index e1d197fb1d..bb2213aa31 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -39,11 +39,9 @@ namespace osu.Game.Rulesets.Osu.Mods protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject drawableOsu)) + if (!(drawable is DrawableOsuHitObject)) return; - var h = drawableOsu.HitObject; - //todo: expose and hide spinner background somehow switch (drawable) From 9caa7ff64dc12e6cd6f536ef7cab9a57fa339a55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 13:37:16 +0900 Subject: [PATCH 182/322] Remove debug endpoint --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 43bc8ff71b..97901184c7 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -80,11 +80,7 @@ namespace osu.Game.Online.Spectator } } -#if DEBUG - private const string endpoint = "http://localhost:5009/spectator"; -#else private const string endpoint = "https://spectator.ppy.sh/spectator"; -#endif private async Task connect() { From ac13a1d21708b7f54a53d818a7e5ac16f0c35936 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 14:27:55 +0900 Subject: [PATCH 183/322] Adjust a couple of flag scales to match previous display size --- osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs | 2 +- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs index 119f71ebfa..cd252392ba 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components AcronymText.Origin = Anchor.TopCentre; AcronymText.Text = team.Acronym.Value.ToUpperInvariant(); AcronymText.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 10); - Flag.Scale = new Vector2(0.5f); + Flag.Scale = new Vector2(0.48f); InternalChildren = new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index ba577888d8..bb1e4d2eff 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components this.losers = losers; Size = new Vector2(150, 40); - Flag.Scale = new Vector2(0.55f); + Flag.Scale = new Vector2(0.54f); Flag.Anchor = Flag.Origin = Anchor.CentreLeft; AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; From e941f2fb711d5d57308203c788a9d594e83bae0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 15:24:12 +0900 Subject: [PATCH 184/322] Fix playback not being smooth (and event unbinding logic) --- .../Visual/Gameplay/TestSceneSpectator.cs | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index f8b5d385a9..4db9d955d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +13,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; +using osu.Framework.Logging; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; @@ -41,6 +44,10 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; + private readonly ManualClock manualClock = new ManualClock(); + + private OsuSpriteText latencyDisplay; + [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -66,15 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }, true); - streamingClient.OnNewFrames += (userId, frames) => - { - foreach (var legacyFrame in frames.Frames) - { - var frame = new TestReplayFrame(); - frame.FromLegacy(legacyFrame, null, null); - replay.Frames.Add(frame); - } - }; + streamingClient.OnNewFrames += onNewFrames; Add(new GridContainer { @@ -115,6 +114,7 @@ namespace osu.Game.Tests.Visual.Gameplay { playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { + Clock = new FramedClock(manualClock), ReplayInputHandler = new TestFramedReplayInputHandler(replay) { GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), @@ -143,8 +143,22 @@ namespace osu.Game.Tests.Visual.Gameplay } } }); + + Add(latencyDisplay = new OsuSpriteText()); }); + private void onNewFrames(int userId, FrameDataBundle frames) + { + Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); + + foreach (var legacyFrame in frames.Frames) + { + var frame = new TestReplayFrame(); + frame.FromLegacy(legacyFrame, null, null); + replay.Frames.Add(frame); + } + } + [Test] public void TestBasic() { @@ -153,13 +167,30 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void Update() { base.Update(); - playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); + + double elapsed = Time.Elapsed; + double? time = playbackManager?.ReplayInputHandler.SetFrameFromTime(manualClock.CurrentTime + elapsed); + + if (time != null) + { + manualClock.CurrentTime = time.Value; + + latencyDisplay.Text = $"latency: {Time.Current - time.Value:N1}ms"; + } + else + { + manualClock.CurrentTime = Time.Current; + } } [TearDownSteps] public void TearDown() { - AddStep("stop recorder", () => recorder.Expire()); + AddStep("stop recorder", () => + { + recorder.Expire(); + streamingClient.OnNewFrames -= onNewFrames; + }); } public class TestFramedReplayInputHandler : FramedReplayInputHandler From 8508d5f8b94d04a373f49b087d739ee4c93bbf62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 15:24:28 +0900 Subject: [PATCH 185/322] Rename test scene to match purpose --- .../{TestSceneSpectator.cs => TestSceneSpectatorPlayback.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneSpectator.cs => TestSceneSpectatorPlayback.cs} (99%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs similarity index 99% rename from osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4db9d955d4..2656b7929c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -31,7 +31,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSpectator : OsuManualInputManagerTestScene + public class TestSceneSpectatorPlayback : OsuManualInputManagerTestScene { protected override bool UseOnlineAPI => true; From f5dbaa9b0fab2bf2b4b805cec6d914897219ff3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 15:25:09 +0900 Subject: [PATCH 186/322] Only watch local user to prevent conflict between testers --- .../Gameplay/TestSceneSpectatorPlayback.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 2656b7929c..e7b7950ad2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -18,6 +18,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; @@ -48,6 +49,9 @@ namespace osu.Game.Tests.Visual.Gameplay private OsuSpriteText latencyDisplay; + [Resolved] + private IAPIProvider api { get; set; } + [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -63,12 +67,20 @@ namespace osu.Game.Tests.Visual.Gameplay { case NotifyCollectionChangedAction.Add: foreach (int user in args.NewItems) - streamingClient.WatchUser(user); + { + if (user == api.LocalUser.Value.Id) + streamingClient.WatchUser(user); + } + break; case NotifyCollectionChangedAction.Remove: foreach (int user in args.OldItems) - streamingClient.StopWatchingUser(user); + { + if (user == api.LocalUser.Value.Id) + streamingClient.StopWatchingUser(user); + } + break; } }, true); From dfe07271de741378553f6fc872f0072e5a050979 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 16:31:39 +0900 Subject: [PATCH 187/322] Add very basic latency handling to spectator test --- .../Gameplay/TestSceneSpectatorPlayback.cs | 43 ++++++++++++++----- .../Spectator/SpectatorStreamingClient.cs | 9 ++-- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index e7b7950ad2..d27a41acd4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.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.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -18,6 +19,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Handlers; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; @@ -49,6 +51,8 @@ namespace osu.Game.Tests.Visual.Gameplay private OsuSpriteText latencyDisplay; + private TestFramedReplayInputHandler replayHandler; + [Resolved] private IAPIProvider api { get; set; } @@ -127,7 +131,7 @@ namespace osu.Game.Tests.Visual.Gameplay playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Clock = new FramedClock(manualClock), - ReplayInputHandler = new TestFramedReplayInputHandler(replay) + ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) { GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), }, @@ -176,22 +180,41 @@ namespace osu.Game.Tests.Visual.Gameplay { } + private double latency = SpectatorStreamingClient.TIME_BETWEEN_SENDS; + protected override void Update() { base.Update(); - double elapsed = Time.Elapsed; - double? time = playbackManager?.ReplayInputHandler.SetFrameFromTime(manualClock.CurrentTime + elapsed); + if (latencyDisplay == null) return; - if (time != null) - { - manualClock.CurrentTime = time.Value; - - latencyDisplay.Text = $"latency: {Time.Current - time.Value:N1}ms"; - } - else + // propagate initial time value + if (manualClock.CurrentTime == 0) { manualClock.CurrentTime = Time.Current; + return; + } + + if (replayHandler.NextFrame != null) + { + var lastFrame = replay.Frames.LastOrDefault(); + + // this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved). + // in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation. + if (lastFrame != null) + latency = Math.Max(latency, Time.Current - lastFrame.Time); + + latencyDisplay.Text = $"latency: {latency:N1}"; + + double proposedTime = Time.Current - latency + Time.Elapsed; + + // this will either advance by one or zero frames. + double? time = replayHandler.SetFrameFromTime(proposedTime); + + if (time == null) + return; + + manualClock.CurrentTime = time.Value; } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 97901184c7..73a18b03b2 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -24,6 +24,11 @@ namespace osu.Game.Online.Spectator { public class SpectatorStreamingClient : Component, ISpectatorClient { + /// + /// The maximum milliseconds between frame bundle sends. + /// + public const double TIME_BETWEEN_SENDS = 200; + private HubConnection connection; private readonly List watchingUsers = new List(); @@ -229,15 +234,13 @@ namespace osu.Game.Online.Spectator private Task lastSend; - private const double time_between_sends = 200; - private const int max_pending_frames = 30; protected override void Update() { base.Update(); - if (pendingFrames.Count > 0 && Time.Current - lastSendTime > time_between_sends) + if (pendingFrames.Count > 0 && Time.Current - lastSendTime > TIME_BETWEEN_SENDS) purgePendingFrames(); } From b1a88a49935c1c497113e5f4de843b72199130a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 16:34:30 +0900 Subject: [PATCH 188/322] Remove extra using --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index d27a41acd4..ad11ac45dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -19,7 +19,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Input.Handlers; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; From 704f8cc4f27b81c92906332a4ac13931cf341c57 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 26 Oct 2020 18:03:04 +0100 Subject: [PATCH 189/322] Fix selection box wandering off into the distance --- .../Timeline/TimelineBlueprintContainer.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 84328466c3..b76032709f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -107,7 +108,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled = handleScrollViaDrag }; - protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); + protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect, this); private void handleScrollViaDrag(DragEvent e) { @@ -138,11 +139,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private class TimelineDragBox : DragBox { private Vector2 lastMouseDown; + private float? lastZoom; private float localMouseDown; - public TimelineDragBox(Action performSelect) + private readonly TimelineBlueprintContainer parent; + + public TimelineDragBox(Action performSelect, TimelineBlueprintContainer parent) : base(performSelect) { + this.parent = parent; } protected override Drawable CreateBox() => new Box @@ -158,8 +163,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { lastMouseDown = e.ScreenSpaceMouseDownPosition; localMouseDown = e.MouseDownPosition.X; + lastZoom = null; } + //Zooming the timeline shifts the coordinate system this compensates for this shift + float zoomCorrection = lastZoom.HasValue ? (parent.timeline.Zoom / lastZoom.Value) : 1; + localMouseDown *= zoomCorrection; + lastZoom = parent.timeline.Zoom; + float selection1 = localMouseDown; float selection2 = e.MousePosition.X; From ead3c195674a73f054dabccf0cfdb0d7f193bd58 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 13:40:42 -0500 Subject: [PATCH 190/322] added function so circle is deleted when shift+right click --- .../Compose/Components/SelectionHandler.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 4caceedc5a..54e62649e1 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.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Audio; using osu.Game.Graphics; @@ -32,6 +33,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { + private bool shiftPressed; + public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -164,6 +167,17 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be reversed. public virtual bool HandleReverse() => false; + protected override bool OnKeyDown(KeyDownEvent e) + { + shiftPressed = e.ShiftPressed; + return false; + } + + protected override void OnKeyUp(KeyUpEvent e) + { + shiftPressed = e.ShiftPressed; + } + public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) @@ -455,6 +469,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { get { + if (shiftPressed) + { + deleteSelected(); + return null; + } + if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); From 123967056693f0f4b34d0eb04dc103ec39a33548 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:28:53 -0500 Subject: [PATCH 191/322] moved right click shift delete functionality to HandleSelectionRequested + reduced func size --- .../Compose/Components/SelectionHandler.cs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 54e62649e1..eeeacce4a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -33,7 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - private bool shiftPressed; public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -167,17 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be reversed. public virtual bool HandleReverse() => false; - protected override bool OnKeyDown(KeyDownEvent e) - { - shiftPressed = e.ShiftPressed; - return false; - } - - protected override void OnKeyUp(KeyUpEvent e) - { - shiftPressed = e.ShiftPressed; - } - public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) @@ -237,6 +226,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) + { + shiftClickDeleteCheck(blueprint, state); + multiSelectionHandler(blueprint, state); + + } + + private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -255,6 +251,15 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private void shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) + { + if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) + { + EditorBeatmap.Remove(blueprint.HitObject); + return; + } + } + private void deleteSelected() { EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); @@ -469,12 +474,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { get { - if (shiftPressed) - { - deleteSelected(); - return null; - } - if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); From ccaf6560ec619004ddd67b57dd62674f7b6520db Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:30:37 -0500 Subject: [PATCH 192/322] formatting --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index eeeacce4a7..f4b98c66b1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -229,7 +228,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { shiftClickDeleteCheck(blueprint, state); multiSelectionHandler(blueprint, state); - } private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) From 255bb9d10092ede439c0d8c5f71b7ca707880a37 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:52:59 -0500 Subject: [PATCH 193/322] fixed issue with returns --- .../Edit/Compose/Components/SelectionHandler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f4b98c66b1..f0a9e69321 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -226,11 +226,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - shiftClickDeleteCheck(blueprint, state); - multiSelectionHandler(blueprint, state); + if (!shiftClickDeleteCheck(blueprint, state)) + handleMultiSelection(blueprint, state); } - private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) + private void handleMultiSelection(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -249,13 +249,14 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private void shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) + private bool shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) { EditorBeatmap.Remove(blueprint.HitObject); - return; + return true; } + return false; } private void deleteSelected() From 3f8c4c57d0682441a69bfed1c5a69bda07ef0979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Oct 2020 22:16:28 +0100 Subject: [PATCH 194/322] Fix code style issues & restructure --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 5 +++ .../Compose/Components/SelectionHandler.cs | 43 ++++++------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 4abdbfc244..f3816f6218 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -120,6 +120,11 @@ namespace osu.Game.Rulesets.Edit /// public void Deselect() => State = SelectionState.NotSelected; + /// + /// Toggles the selection state of this . + /// + public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected; + public bool IsSelected => State == SelectionState.Selected; /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f0a9e69321..9cddb69d0b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Audio; using osu.Game.Graphics; @@ -225,38 +224,22 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) - { - if (!shiftClickDeleteCheck(blueprint, state)) - handleMultiSelection(blueprint, state); - } - - private void handleMultiSelection(SelectionBlueprint blueprint, InputState state) - { - if (state.Keyboard.ControlPressed) - { - if (blueprint.IsSelected) - blueprint.Deselect(); - else - blueprint.Select(); - } - else - { - if (blueprint.IsSelected) - return; - - DeselectAll?.Invoke(); - blueprint.Select(); - } - } - - private bool shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) - { EditorBeatmap.Remove(blueprint.HitObject); - return true; - } - return false; + else if (state.Keyboard.ControlPressed) + blueprint.ToggleSelection(); + else + ensureSelected(blueprint); + } + + private void ensureSelected(SelectionBlueprint blueprint) + { + if (blueprint.IsSelected) + return; + + DeselectAll?.Invoke(); + blueprint.Select(); } private void deleteSelected() From 7392876b5f71a5bfb4b22fcabb0a0e38cef5a368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Oct 2020 00:05:03 +0100 Subject: [PATCH 195/322] Fix mania crashing due to spectator client handling frames with unconverted beatmap --- .../Visual/Gameplay/TestSceneReplayRecorder.cs | 6 ++++++ .../Visual/Gameplay/TestSceneReplayRecording.cs | 6 ++++++ .../Visual/Gameplay/TestSceneSpectatorPlayback.cs | 4 ++++ .../Online/Spectator/SpectatorStreamingClient.cs | 14 +++++++++----- osu.Game/Rulesets/UI/ReplayRecorder.cs | 7 +++---- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index e964d2a40e..47dd47959d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,11 +13,13 @@ using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Framework.Testing; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -33,6 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; + [Cached] + private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index c0f99db85d..6872b6a669 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -2,17 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; +using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -25,6 +28,9 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly TestRulesetInputManager recordingManager; + [Cached] + private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + public TestSceneReplayRecording() { Replay replay = new Replay(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index ad11ac45dd..1d8231cce7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -58,6 +59,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private SpectatorStreamingClient streamingClient { get; set; } + [Cached] + private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 73a18b03b2..7059818b4e 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; @@ -19,6 +20,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; +using osu.Game.Screens.Play; namespace osu.Game.Online.Spectator { @@ -44,8 +46,8 @@ namespace osu.Game.Online.Spectator [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private IBindable beatmap { get; set; } + [CanBeNull] + private IBeatmap currentBeatmap; [Resolved] private IBindable ruleset { get; set; } @@ -167,7 +169,7 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } - public void BeginPlaying() + public void BeginPlaying(GameplayBeatmap beatmap) { if (isPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); @@ -175,10 +177,11 @@ namespace osu.Game.Online.Spectator isPlaying = true; // transfer state at point of beginning play - currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; + currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; currentState.RulesetID = ruleset.Value.ID; currentState.Mods = mods.Value.Select(m => new APIMod(m)); + currentBeatmap = beatmap.PlayableBeatmap; beginPlaying(); } @@ -201,6 +204,7 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { isPlaying = false; + currentBeatmap = null; if (!isConnected) return; @@ -247,7 +251,7 @@ namespace osu.Game.Online.Spectator public void HandleFrame(ReplayFrame frame) { if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(beatmap.Value.Beatmap)); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index a84b4f4ba8..1438ebd37a 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -5,15 +5,14 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets.Replays; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.UI @@ -33,7 +32,7 @@ namespace osu.Game.Rulesets.UI private SpectatorStreamingClient spectatorStreaming { get; set; } [Resolved] - private IBindable beatmap { get; set; } + private GameplayBeatmap gameplayBeatmap { get; set; } protected ReplayRecorder(Replay target) { @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.UI inputManager = GetContainingInputManager(); - spectatorStreaming?.BeginPlaying(); + spectatorStreaming?.BeginPlaying(gameplayBeatmap); } protected override void Dispose(bool isDisposing) From 68719bb23df43e7d18cd429042f05f240e432495 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 10:59:24 +0900 Subject: [PATCH 196/322] Rename other variables to match --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 7059818b4e..5a41316f31 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -50,10 +50,10 @@ namespace osu.Game.Online.Spectator private IBeatmap currentBeatmap; [Resolved] - private IBindable ruleset { get; set; } + private IBindable currentRuleset { get; set; } [Resolved] - private IBindable> mods { get; set; } + private IBindable> currentMods { get; set; } private readonly SpectatorState currentState = new SpectatorState(); @@ -178,8 +178,8 @@ namespace osu.Game.Online.Spectator // transfer state at point of beginning play currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; - currentState.RulesetID = ruleset.Value.ID; - currentState.Mods = mods.Value.Select(m => new APIMod(m)); + currentState.RulesetID = currentRuleset.Value.ID; + currentState.Mods = currentMods.Value.Select(m => new APIMod(m)); currentBeatmap = beatmap.PlayableBeatmap; beginPlaying(); From e1f578c590788235640154dd825df2a1ade4e492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 12:28:10 +0900 Subject: [PATCH 197/322] Change editor timing screen seek behaviour to only occur on clicking table rows Previously it would react to any selection changed event, which could in lude time changes (which is done by removing then adding the ControlPointGroup). Closes #10590. --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 9 ++++++++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 14 -------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index c8982b819a..64f9526816 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -177,6 +177,9 @@ namespace osu.Game.Screens.Edit.Timing private readonly Box hoveredBackground; + [Resolved] + private EditorClock clock { get; set; } + [Resolved] private Bindable selectedGroup { get; set; } @@ -200,7 +203,11 @@ namespace osu.Game.Screens.Edit.Timing }, }; - Action = () => selectedGroup.Value = controlGroup; + Action = () => + { + selectedGroup.Value = controlGroup; + clock.SeekTo(controlGroup.Time); + }; } private Color4 colourHover; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0796097186..f511382cde 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -22,9 +22,6 @@ namespace osu.Game.Screens.Edit.Timing [Cached] private Bindable selectedGroup = new Bindable(); - [Resolved] - private EditorClock clock { get; set; } - public TimingScreen() : base(EditorScreenMode.Timing) { @@ -48,17 +45,6 @@ namespace osu.Game.Screens.Edit.Timing } }; - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedGroup.BindValueChanged(selected => - { - if (selected.NewValue != null) - clock.SeekTo(selected.NewValue.Time); - }); - } - protected override void OnTimelineLoaded(TimelineArea timelineArea) { base.OnTimelineLoaded(timelineArea); From 27c1a4c4d3693dbc1f22b2c9427646c8ccf01977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 12:53:54 +0900 Subject: [PATCH 198/322] Move right-click deletion logic to be handled at a SelectionBlueprint level --- .../Sliders/SliderSelectionBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 +++++++++++++++++ .../Edit/Compose/Components/SelectionHandler.cs | 4 +--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index d3fb5defae..ca9ec886d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -107,14 +107,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case MouseButton.Right: rightClickPosition = e.MouseDownPosition; - return false; // Allow right click to be handled by context menu + break; case MouseButton.Left when e.ControlPressed && IsSelected: placementControlPointIndex = addControlPoint(e.MousePosition); return true; // Stop input from being handled and modifying the selection } - return false; + return base.OnMouseDown(e); } private int? placementControlPointIndex; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index f3816f6218..87ef7e647f 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,10 +8,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -52,6 +55,20 @@ namespace osu.Game.Rulesets.Edit updateState(); } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.CurrentState.Keyboard.ShiftPressed && e.IsPressed(MouseButton.Right)) + { + editorBeatmap.Remove(HitObject); + return true; + } + + return base.OnMouseDown(e); + } + private SelectionState state; public event Action StateChanged; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9cddb69d0b..036edbeb84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -225,9 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) - EditorBeatmap.Remove(blueprint.HitObject); - else if (state.Keyboard.ControlPressed) + if (state.Keyboard.ControlPressed) blueprint.ToggleSelection(); else ensureSelected(blueprint); From 3c2e2f29bc8eb190daab56fea86ab00a1925871b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 13:17:44 +0900 Subject: [PATCH 199/322] Remove unused using statement --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 036edbeb84..24f88bf36d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { From 6853da459dde1a1f90a0362113cd3121325d2ed7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 13:54:33 +0900 Subject: [PATCH 200/322] Move sample pausing logic out of FrameStabilityContainer --- .../TestSceneGameplaySamplePlayback.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +- .../Rulesets/UI/FrameStabilityContainer.cs | 49 +++---------------- osu.Game/Rulesets/UI/FrameStableClock.cs | 28 +++++++++++ osu.Game/Screens/Play/Player.cs | 14 ++++-- 5 files changed, 47 insertions(+), 50 deletions(-) create mode 100644 osu.Game/Rulesets/UI/FrameStableClock.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 6e505b16c2..e2b867bfb2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("get variables", () => { - gameplayClock = Player.ChildrenOfType().First(); + gameplayClock = Player as ISamplePlaybackDisabler; slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); samples = slider.ChildrenOfType().ToArray(); }); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 50e9a93e22..3f967d489b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.UI public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + public override FrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; private bool frameStablePlayback = true; @@ -404,7 +404,7 @@ namespace osu.Game.Rulesets.UI /// /// The frame-stable clock which is being used for playfield display. /// - public abstract GameplayClock FrameStableClock { get; } + public abstract FrameStableClock FrameStableClock { get; } /// ~ /// The associated ruleset. diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index e4a3a2fe3d..4ea5b514c9 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,10 +2,7 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -18,11 +15,8 @@ namespace osu.Game.Rulesets.UI /// A container which consumes a parent gameplay clock and standardises frame counts for children. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// - [Cached(typeof(ISamplePlaybackDisabler))] - public class FrameStabilityContainer : Container, IHasReplayHandler, ISamplePlaybackDisabler + public class FrameStabilityContainer : Container, IHasReplayHandler { - private readonly Bindable samplePlaybackDisabled = new Bindable(); - private readonly double gameplayStartTime; /// @@ -35,16 +29,14 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback = true; - public GameplayClock GameplayClock => stabilityGameplayClock; - [Cached(typeof(GameplayClock))] - private readonly StabilityGameplayClock stabilityGameplayClock; + public readonly FrameStableClock FrameStableClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - stabilityGameplayClock = new StabilityGameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + FrameStableClock = new FrameStableClock(framedClock = new FramedClock(manualClock = new ManualClock())); this.gameplayStartTime = gameplayStartTime; } @@ -65,12 +57,9 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; - GameplayClock.IsPaused.BindTo(clock.IsPaused); + parentGameplayClock = FrameStableClock.ParentGameplayClock = clock; + FrameStableClock.IsPaused.BindTo(clock.IsPaused); } - - // this is a bit temporary. should really be done inside of GameplayClock (but requires large structural changes). - stabilityGameplayClock.ParentSampleDisabler = sampleDisabler; } protected override void LoadComplete() @@ -102,9 +91,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = !GameplayClock.IsPaused.Value; - - samplePlaybackDisabled.Value = stabilityGameplayClock.ShouldDisableSamplePlayback; + validState = !FrameStableClock.IsPaused.Value; int loops = 0; @@ -222,32 +209,10 @@ namespace osu.Game.Rulesets.UI } else { - Clock = GameplayClock; + Clock = FrameStableClock; } } public ReplayInputHandler ReplayInputHandler { get; set; } - - IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; - - private class StabilityGameplayClock : GameplayClock - { - public GameplayClock ParentGameplayClock; - - public ISamplePlaybackDisabler ParentSampleDisabler; - - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); - - public StabilityGameplayClock(FramedClock underlyingClock) - : base(underlyingClock) - { - } - - public override bool ShouldDisableSamplePlayback => - // handle the case where playback is catching up to real-time. - base.ShouldDisableSamplePlayback - || ParentSampleDisabler?.SamplePlaybackDisabled.Value == true - || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); - } } } diff --git a/osu.Game/Rulesets/UI/FrameStableClock.cs b/osu.Game/Rulesets/UI/FrameStableClock.cs new file mode 100644 index 0000000000..5c81ce3093 --- /dev/null +++ b/osu.Game/Rulesets/UI/FrameStableClock.cs @@ -0,0 +1,28 @@ +// 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.Bindables; +using osu.Framework.Timing; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + public class FrameStableClock : GameplayClock + { + public GameplayClock ParentGameplayClock; + + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); + + public FrameStableClock(FramedClock underlyingClock) + : base(underlyingClock) + { + } + + public override bool ShouldDisableSamplePlayback => + // handle the case where playback is catching up to real-time. + base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6b2d2f40d0..b0923ed4c8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -238,11 +238,8 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(paused => - { - updateGameplayState(); - samplePlaybackDisabled.Value = paused.NewValue; - }); + DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -370,6 +367,13 @@ namespace osu.Game.Screens.Play } }; + protected override void Update() + { + base.Update(); + + samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.ShouldDisableSamplePlayback; + } + private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { updateGameplayState(); From 9cfb81589e796d7153d380178fd413c7a794810f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:10:12 +0900 Subject: [PATCH 201/322] Use bindable flow instead --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +-- .../Rulesets/UI/FrameStabilityContainer.cs | 35 +++++++++++++++---- osu.Game/Rulesets/UI/FrameStableClock.cs | 19 ++-------- osu.Game/Screens/Play/GameplayClock.cs | 5 --- osu.Game/Screens/Play/Player.cs | 20 ++++++----- 6 files changed, 46 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 2263e2b2f4..6e7025847a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuInputManager inputManager; - private GameplayClock gameplayClock; + private IFrameStableClock gameplayClock; private List replayFrames; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 3f967d489b..f6cf836fe7 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.UI public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override FrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; + public override IFrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; private bool frameStablePlayback = true; @@ -404,7 +404,7 @@ namespace osu.Game.Rulesets.UI /// /// The frame-stable clock which is being used for playfield display. /// - public abstract FrameStableClock FrameStableClock { get; } + public abstract IFrameStableClock FrameStableClock { get; } /// ~ /// The associated ruleset. diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4ea5b514c9..9ffbce991c 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,7 +2,10 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -29,14 +32,16 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback = true; + public IFrameStableClock FrameStableClock => frameStableClock; + [Cached(typeof(GameplayClock))] - public readonly FrameStableClock FrameStableClock; + private readonly FrameStabilityClock frameStableClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - FrameStableClock = new FrameStableClock(framedClock = new FramedClock(manualClock = new ManualClock())); + frameStableClock = new FrameStabilityClock(framedClock = new FramedClock(manualClock = new ManualClock())); this.gameplayStartTime = gameplayStartTime; } @@ -57,8 +62,8 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - parentGameplayClock = FrameStableClock.ParentGameplayClock = clock; - FrameStableClock.IsPaused.BindTo(clock.IsPaused); + parentGameplayClock = frameStableClock.ParentGameplayClock = clock; + frameStableClock.IsPaused.BindTo(clock.IsPaused); } } @@ -91,7 +96,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = !FrameStableClock.IsPaused.Value; + validState = !frameStableClock.IsPaused.Value; int loops = 0; @@ -194,6 +199,8 @@ namespace osu.Game.Rulesets.UI requireMoreUpdateLoops |= manualClock.CurrentTime != parentGameplayClock.CurrentTime; + frameStableClock.IsCatchingUp.Value = requireMoreUpdateLoops; + // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed framedClock.ProcessFrame(); @@ -209,10 +216,26 @@ namespace osu.Game.Rulesets.UI } else { - Clock = FrameStableClock; + Clock = frameStableClock; } } public ReplayInputHandler ReplayInputHandler { get; set; } + + private class FrameStabilityClock : GameplayClock, IFrameStableClock + { + public GameplayClock ParentGameplayClock; + + public readonly Bindable IsCatchingUp = new Bindable(); + + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); + + public FrameStabilityClock(FramedClock underlyingClock) + : base(underlyingClock) + { + } + + IBindable IFrameStableClock.IsCatchingUp => IsCatchingUp; + } } } diff --git a/osu.Game/Rulesets/UI/FrameStableClock.cs b/osu.Game/Rulesets/UI/FrameStableClock.cs index 5c81ce3093..d888eefdc6 100644 --- a/osu.Game/Rulesets/UI/FrameStableClock.cs +++ b/osu.Game/Rulesets/UI/FrameStableClock.cs @@ -1,28 +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; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; -using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { - public class FrameStableClock : GameplayClock + public interface IFrameStableClock : IFrameBasedClock { - public GameplayClock ParentGameplayClock; - - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); - - public FrameStableClock(FramedClock underlyingClock) - : base(underlyingClock) - { - } - - public override bool ShouldDisableSamplePlayback => - // handle the case where playback is catching up to real-time. - base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); + IBindable IsCatchingUp { get; } } } diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4d0872e5bb..db4b5d300b 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -61,11 +61,6 @@ namespace osu.Game.Screens.Play public bool IsRunning => underlyingClock.IsRunning; - /// - /// Whether nested samples supporting the interface should be paused. - /// - public virtual bool ShouldDisableSamplePlayback => IsPaused.Value; - public void ProcessFrame() { // intentionally not updating the underlying clock (handled externally). diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b0923ed4c8..3c0c643413 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -238,7 +238,13 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.IsPaused.BindValueChanged(paused => + { + updateGameplayState(); + updateSampleDisabledState(); + }); + + DrawableRuleset.FrameStableClock.IsCatchingUp.BindValueChanged(_ => updateSampleDisabledState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); @@ -367,13 +373,6 @@ namespace osu.Game.Screens.Play } }; - protected override void Update() - { - base.Update(); - - samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.ShouldDisableSamplePlayback; - } - private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { updateGameplayState(); @@ -388,6 +387,11 @@ namespace osu.Game.Screens.Play LocalUserPlaying.Value = inGameplay; } + private void updateSampleDisabledState() + { + samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.IsCatchingUp.Value || GameplayClockContainer.GameplayClock.IsPaused.Value; + } + private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value From 09087faf3b78908c912b1384014782705bda1228 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:23:24 +0900 Subject: [PATCH 202/322] Fix non-matching filename --- .../Rulesets/UI/{FrameStableClock.cs => IFrameStableClock.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Rulesets/UI/{FrameStableClock.cs => IFrameStableClock.cs} (100%) diff --git a/osu.Game/Rulesets/UI/FrameStableClock.cs b/osu.Game/Rulesets/UI/IFrameStableClock.cs similarity index 100% rename from osu.Game/Rulesets/UI/FrameStableClock.cs rename to osu.Game/Rulesets/UI/IFrameStableClock.cs From 606a4304a85a8c8299a5de5dde08acdd9ac26d06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:33:12 +0900 Subject: [PATCH 203/322] Remove unused usings --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 1 - .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 6e7025847a..8c819c4773 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index e2b867bfb2..af00322cbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -9,7 +9,6 @@ using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; From e0ad005cc1c49cc9e9389d4e10df3a65404d7df7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:31:56 +0900 Subject: [PATCH 204/322] Move editor sample disabling logic to editor class (and support screen switching) --- osu.Game/Screens/Edit/Editor.cs | 78 +++++++++++++++++----------- osu.Game/Screens/Edit/EditorClock.cs | 17 +++--- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c3560dff38..25ebd55f81 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -43,8 +43,9 @@ using osuTK.Input; namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] + [Cached(typeof(ISamplePlaybackDisabler))] [Cached] - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler { public override float BackgroundParallaxAmount => 0.1f; @@ -64,6 +65,10 @@ namespace osu.Game.Screens.Edit [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + + private readonly Bindable samplePlaybackDisabled = new Bindable(); + private bool exitConfirmed; private string lastSavedHash; @@ -109,9 +114,10 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); - dependencies.CacheAs(clock); AddInternal(clock); + clock.SeekingOrStopped.BindValueChanged(_ => updateSampleDisabledState()); + // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); @@ -557,40 +563,52 @@ namespace osu.Game.Screens.Edit .ScaleTo(0.98f, 200, Easing.OutQuint) .FadeOut(200, Easing.OutQuint); - if ((currentScreen = screenContainer.SingleOrDefault(s => s.Type == e.NewValue)) != null) + try { - screenContainer.ChangeChildDepth(currentScreen, lastScreen?.Depth + 1 ?? 0); + if ((currentScreen = screenContainer.SingleOrDefault(s => s.Type == e.NewValue)) != null) + { + screenContainer.ChangeChildDepth(currentScreen, lastScreen?.Depth + 1 ?? 0); - currentScreen - .ScaleTo(1, 200, Easing.OutQuint) - .FadeIn(200, Easing.OutQuint); - return; + currentScreen + .ScaleTo(1, 200, Easing.OutQuint) + .FadeIn(200, Easing.OutQuint); + return; + } + + switch (e.NewValue) + { + case EditorScreenMode.SongSetup: + currentScreen = new SetupScreen(); + break; + + case EditorScreenMode.Compose: + currentScreen = new ComposeScreen(); + break; + + case EditorScreenMode.Design: + currentScreen = new DesignScreen(); + break; + + case EditorScreenMode.Timing: + currentScreen = new TimingScreen(); + break; + } + + LoadComponentAsync(currentScreen, newScreen => + { + if (newScreen == currentScreen) + screenContainer.Add(newScreen); + }); } - - switch (e.NewValue) + finally { - case EditorScreenMode.SongSetup: - currentScreen = new SetupScreen(); - break; - - case EditorScreenMode.Compose: - currentScreen = new ComposeScreen(); - break; - - case EditorScreenMode.Design: - currentScreen = new DesignScreen(); - break; - - case EditorScreenMode.Timing: - currentScreen = new TimingScreen(); - break; + updateSampleDisabledState(); } + } - LoadComponentAsync(currentScreen, newScreen => - { - if (newScreen == currentScreen) - screenContainer.Add(newScreen); - }); + private void updateSampleDisabledState() + { + samplePlaybackDisabled.Value = clock.SeekingOrStopped.Value || !(currentScreen is ComposeScreen); } private void seek(UIEvent e, int direction) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 64ed34f5ec..949636f695 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -11,14 +11,13 @@ using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISamplePlaybackDisabler + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { public IBindable Track => track; @@ -32,9 +31,9 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; - public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + public IBindable SeekingOrStopped => seekingOrStopped; - private readonly Bindable samplePlaybackDisabled = new Bindable(); + private readonly Bindable seekingOrStopped = new Bindable(true); public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) @@ -171,13 +170,13 @@ namespace osu.Game.Screens.Edit public void Stop() { - samplePlaybackDisabled.Value = true; + seekingOrStopped.Value = true; underlyingClock.Stop(); } public bool Seek(double position) { - samplePlaybackDisabled.Value = true; + seekingOrStopped.Value = true; ClearTransforms(); return underlyingClock.Seek(position); @@ -228,7 +227,7 @@ namespace osu.Game.Screens.Edit private void updateSeekingState() { - if (samplePlaybackDisabled.Value) + if (seekingOrStopped.Value) { if (track.Value?.IsRunning != true) { @@ -240,13 +239,13 @@ namespace osu.Game.Screens.Edit // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - samplePlaybackDisabled.Value = Transforms.Any(); + seekingOrStopped.Value = Transforms.Any(); } } public void SeekTo(double seekDestination) { - samplePlaybackDisabled.Value = true; + seekingOrStopped.Value = true; if (IsRunning) Seek(seekDestination); From 03d566da356cf96199766229d05ab724a0709ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:35:12 +0900 Subject: [PATCH 205/322] Rename test variable and remove unncessary cast --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index af00322cbc..b86cb69eb4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -21,11 +21,11 @@ namespace osu.Game.Tests.Visual.Gameplay { DrawableSlider slider = null; DrawableSample[] samples = null; - ISamplePlaybackDisabler gameplayClock = null; + ISamplePlaybackDisabler sampleDisabler = null; AddStep("get variables", () => { - gameplayClock = Player as ISamplePlaybackDisabler; + sampleDisabler = Player; slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); samples = slider.ChildrenOfType().ToArray(); }); @@ -42,16 +42,16 @@ namespace osu.Game.Tests.Visual.Gameplay return true; }); - AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); // because we are in frame stable context, it's quite likely that not all samples are "played" at this point. // the important thing is that at least one started, and that sample has since stopped. AddAssert("all looping samples stopped immediately", () => allStopped(allLoopingSounds)); AddUntilStep("all samples stopped eventually", () => allStopped(allSounds)); - AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + AddAssert("sample playback still disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); - AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value); + AddUntilStep("seek finished, sample playback enabled", () => !sampleDisabler.SamplePlaybackDisabled.Value); AddUntilStep("any sample is playing", () => Player.ChildrenOfType().Any(s => s.IsPlaying)); } From b8beac27cee305d5c8003e66af02993dd9a4d956 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 17:14:41 +0900 Subject: [PATCH 206/322] Use previous logic for catching-up mode --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 9ffbce991c..28b7975a89 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -197,9 +197,11 @@ namespace osu.Game.Rulesets.UI manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; manualClock.IsRunning = parentGameplayClock.IsRunning; - requireMoreUpdateLoops |= manualClock.CurrentTime != parentGameplayClock.CurrentTime; + double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); - frameStableClock.IsCatchingUp.Value = requireMoreUpdateLoops; + requireMoreUpdateLoops |= timeBehind != 0; + + frameStableClock.IsCatchingUp.Value = timeBehind > 200; // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed From 064c50c3ac8642803c91b6a08a11ea5d64f1d796 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 27 Oct 2020 12:39:50 +0100 Subject: [PATCH 207/322] Expose currentZoom to fix selection box wiggle --- .../Components/Timeline/TimelineBlueprintContainer.cs | 10 +++++----- .../Components/Timeline/ZoomableScrollContainer.cs | 8 +++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index b76032709f..008da14a21 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -139,6 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private class TimelineDragBox : DragBox { private Vector2 lastMouseDown; + private float? lastZoom; private float localMouseDown; @@ -166,13 +166,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline lastZoom = null; } - //Zooming the timeline shifts the coordinate system this compensates for this shift - float zoomCorrection = lastZoom.HasValue ? (parent.timeline.Zoom / lastZoom.Value) : 1; + //Zooming the timeline shifts the coordinate system. zoomCorrection compensates for that + float zoomCorrection = lastZoom.HasValue ? (parent.timeline.CurrentZoom / lastZoom.Value) : 1; localMouseDown *= zoomCorrection; - lastZoom = parent.timeline.Zoom; + lastZoom = parent.timeline.CurrentZoom; float selection1 = localMouseDown; - float selection2 = e.MousePosition.X; + float selection2 = e.MousePosition.X * zoomCorrection; Box.X = Math.Min(selection1, selection2); Box.Width = Math.Abs(selection1 - selection2); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 227eecf9c7..6a9552a2c4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -29,9 +29,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Container zoomedContent; protected override Container Content => zoomedContent; - private float currentZoom = 1; + /// + /// The current zoom level of . + /// It may differ from during transitions. + /// + public float CurrentZoom => currentZoom; + + [Resolved(canBeNull: true)] private IFrameBasedClock editorClock { get; set; } From 983a2774e89c4e973c28c08bba669e9ae25f3096 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 27 Oct 2020 15:09:10 +0100 Subject: [PATCH 208/322] Code Formatting --- .../Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 6a9552a2c4..f90658e99c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -37,7 +37,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public float CurrentZoom => currentZoom; - [Resolved(canBeNull: true)] private IFrameBasedClock editorClock { get; set; } From 742a96484befff6d8c137cd749cfd648c7c65992 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 20:13:18 +0300 Subject: [PATCH 209/322] Add ability to set extra parameters to SearchBeatmapSetsRequest --- .../API/Requests/SearchBeatmapSetsRequest.cs | 28 ++++++++++++++++++- .../Overlays/BeatmapListing/SearchExtra.cs | 13 +++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/BeatmapListing/SearchExtra.cs diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index dde45b5aeb..f8cf747757 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -21,6 +21,8 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } + public SearchExtra Extra { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -35,7 +37,8 @@ namespace osu.Game.Online.API.Requests SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, - SearchLanguage language = SearchLanguage.Any) + SearchLanguage language = SearchLanguage.Any, + SearchExtra extra = SearchExtra.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -46,6 +49,7 @@ namespace osu.Game.Online.API.Requests SortDirection = sortDirection; Genre = genre; Language = language; + Extra = extra; } protected override WebRequest CreateWebRequest() @@ -68,6 +72,28 @@ namespace osu.Game.Online.API.Requests req.AddCursor(cursor); + if (Extra != SearchExtra.Any) + { + string extraString = string.Empty; + + switch (Extra) + { + case SearchExtra.Both: + extraString = "video.storyboard"; + break; + + case SearchExtra.Storyboard: + extraString = "storyboard"; + break; + + case SearchExtra.Video: + extraString = "video"; + break; + } + + req.AddParameter("e", extraString); + } + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs new file mode 100644 index 0000000000..fd4896c46e --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.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. + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchExtra + { + Video, + Storyboard, + Both, + Any + } +} From 26a60d898cfd3404275ad59021931bf4a03ec94a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 21:22:20 +0300 Subject: [PATCH 210/322] Implement BeatmapSearchExtraFilterRow --- .../TestSceneBeatmapListingSearchControl.cs | 3 + .../BeatmapListingFilterControl.cs | 4 +- .../BeatmapListingSearchControl.cs | 4 + .../BeatmapSearchExtraFilterRow.cs | 97 +++++++++++++ .../BeatmapListing/BeatmapSearchFilterRow.cs | 127 +++++++++--------- .../BeatmapSearchRulesetFilterRow.cs | 3 +- .../Overlays/BeatmapListing/SearchExtra.cs | 4 +- 7 files changed, 176 insertions(+), 66 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index a4698a9a32..9a410dd18c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText category; OsuSpriteText genre; OsuSpriteText language; + OsuSpriteText extra; Add(control = new BeatmapListingSearchControl { @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.UserInterface category = new OsuSpriteText(), genre = new OsuSpriteText(), language = new OsuSpriteText(), + extra = new OsuSpriteText() } }); @@ -54,6 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); } [Test] diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 494a0df8f8..37fbfe7093 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,6 +130,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); @@ -179,7 +180,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.Current.Value, sortControl.SortDirection.Value, searchControl.Genre.Value, - searchControl.Language.Value); + searchControl.Language.Value, + searchControl.Extra.Value); getSetsRequest.Success += response => { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 29c4fe0d2e..437c26e36d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; + public Bindable Extra => extraFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -48,6 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; + private readonly BeatmapSearchExtraFilterRow extraFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -105,6 +108,7 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), + extraFilter = new BeatmapSearchExtraFilterRow() } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs new file mode 100644 index 0000000000..6e81cd2976 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -0,0 +1,97 @@ +// 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; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchExtraFilterRow : BeatmapSearchFilterRow + { + public BeatmapSearchExtraFilterRow() + : base("Extra") + { + } + + protected override Drawable CreateFilter() => new ExtraFilter(); + + private class ExtraFilter : FillFlowContainer, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly ExtraFilterTabItem videoItem; + private readonly ExtraFilterTabItem storyboardItem; + + public ExtraFilter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + Height = 15; + Spacing = new Vector2(10, 0); + AddRange(new[] + { + videoItem = new ExtraFilterTabItem(SearchExtra.Video), + storyboardItem = new ExtraFilterTabItem(SearchExtra.Storyboard) + }); + + foreach (var item in Children) + item.StateUpdated += updateBindable; + } + + private void updateBindable() + { + if (videoItem.Active.Value && storyboardItem.Active.Value) + { + Current.Value = SearchExtra.Both; + return; + } + + if (videoItem.Active.Value) + { + Current.Value = SearchExtra.Video; + return; + } + + if (storyboardItem.Active.Value) + { + Current.Value = SearchExtra.Storyboard; + return; + } + + Current.Value = SearchExtra.Any; + } + } + + private class ExtraFilterTabItem : FilterTabItem + { + public event Action StateUpdated; + + public ExtraFilterTabItem(SearchExtra value) + : base(value) + { + Active.BindValueChanged(_ => StateUpdated?.Invoke()); + } + + protected override bool OnClick(ClickEvent e) + { + base.OnClick(e); + Active.Value = !Active.Value; + return true; + } + + protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 45ef793deb..ad32475b25 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -32,6 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapSearchFilterRow(string headerName) { + Drawable filter; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; AddInternal(new GridContainer @@ -49,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing }, Content = new[] { - new Drawable[] + new[] { new OsuSpriteText { @@ -58,17 +59,17 @@ namespace osu.Game.Overlays.BeatmapListing Font = OsuFont.GetFont(size: 13), Text = headerName.Titleize() }, - CreateFilter().With(f => - { - f.Current = current; - }) + filter = CreateFilter() } } }); + + if (filter is IHasCurrentValue filterWithValue) + filterWithValue.Current = current; } [NotNull] - protected virtual BeatmapSearchFilter CreateFilter() => new BeatmapSearchFilter(); + protected virtual Drawable CreateFilter() => new BeatmapSearchFilter(); protected class BeatmapSearchFilter : TabControl { @@ -99,62 +100,6 @@ namespace osu.Game.Overlays.BeatmapListing protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); - protected class FilterTabItem : TabItem - { - protected virtual float TextSize => 13; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private readonly OsuSpriteText text; - - public FilterTabItem(T value) - : base(value) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - AddRangeInternal(new Drawable[] - { - text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), - Text = (value as Enum)?.GetDescription() ?? value.ToString() - }, - new HoverClickSounds() - }); - - Enabled.Value = true; - } - - [BackgroundDependencyLoader] - private void load() - { - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); - - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; - } - private class FilterDropdown : OsuTabDropdown { protected override DropdownHeader CreateHeader() => new FilterHeader @@ -172,5 +117,63 @@ namespace osu.Game.Overlays.BeatmapListing } } } + + protected class FilterTabItem : TabItem + { + protected virtual float TextSize => 13; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly OsuSpriteText text; + + public FilterTabItem(T value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + AddRangeInternal(new Drawable[] + { + text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Text = CreateText(value) + }, + new HoverClickSounds() + }); + + Enabled.Value = true; + } + + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + + private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index eebd896cf9..a8dc088e52 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override BeatmapSearchFilter CreateFilter() => new RulesetFilter(); + protected override Drawable CreateFilter() => new RulesetFilter(); private class RulesetFilter : BeatmapSearchFilter { diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index fd4896c46e..53900211e1 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -5,9 +5,9 @@ namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { + Any, Video, Storyboard, - Both, - Any + Both } } From 1b40b56d41081be1a199bea0889727360e57de1c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 21:30:53 +0300 Subject: [PATCH 211/322] Add ability to search by play criteria --- .../TestSceneBeatmapListingSearchControl.cs | 5 ++++- .../Online/API/Requests/SearchBeatmapSetsRequest.cs | 9 ++++++++- .../BeatmapListing/BeatmapListingFilterControl.cs | 4 +++- .../BeatmapListing/BeatmapListingSearchControl.cs | 6 +++++- osu.Game/Overlays/BeatmapListing/SearchPlayed.cs | 12 ++++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/SearchPlayed.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 9a410dd18c..5c9431aad1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText genre; OsuSpriteText language; OsuSpriteText extra; + OsuSpriteText played; Add(control = new BeatmapListingSearchControl { @@ -47,7 +48,8 @@ namespace osu.Game.Tests.Visual.UserInterface category = new OsuSpriteText(), genre = new OsuSpriteText(), language = new OsuSpriteText(), - extra = new OsuSpriteText() + extra = new OsuSpriteText(), + played = new OsuSpriteText() } }); @@ -57,6 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); + control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } [Test] diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index f8cf747757..12383e7457 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -23,6 +23,8 @@ namespace osu.Game.Online.API.Requests public SearchExtra Extra { get; } + public SearchPlayed Played { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -38,7 +40,8 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - SearchExtra extra = SearchExtra.Any) + SearchExtra extra = SearchExtra.Any, + SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -50,6 +53,7 @@ namespace osu.Game.Online.API.Requests Genre = genre; Language = language; Extra = extra; + Played = played; } protected override WebRequest CreateWebRequest() @@ -94,6 +98,9 @@ namespace osu.Game.Online.API.Requests req.AddParameter("e", extraString); } + if (Played != SearchPlayed.Any) + req.AddParameter("played", Played.ToString().ToLowerInvariant()); + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 37fbfe7093..3f09a7e3d1 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -131,6 +131,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); @@ -181,7 +182,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.SortDirection.Value, searchControl.Genre.Value, searchControl.Language.Value, - searchControl.Extra.Value); + searchControl.Extra.Value, + searchControl.Played.Value); getSetsRequest.Success += response => { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 437c26e36d..80beed6217 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -30,6 +30,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Extra => extraFilter.Current; + public Bindable Played => playedFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -51,6 +53,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchExtraFilterRow extraFilter; + private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -108,7 +111,8 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchExtraFilterRow() + extraFilter = new BeatmapSearchExtraFilterRow(), + playedFilter = new BeatmapSearchFilterRow(@"Played") } } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs new file mode 100644 index 0000000000..eb7fb46158 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchPlayed + { + Any, + Played, + Unplayed + } +} From 1710b396e7195f34b024166d74129701ffc21eac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 22:27:29 +0300 Subject: [PATCH 212/322] Implement BeatmapSearchMultipleSelectionFilterRow --- .../TestSceneBeatmapListingSearchControl.cs | 3 +- .../API/Requests/SearchBeatmapSetsRequest.cs | 33 ++------ .../BeatmapListingSearchControl.cs | 3 +- .../BeatmapSearchExtraFilterRow.cs | 79 ++---------------- .../BeatmapListing/BeatmapSearchFilterRow.cs | 64 +------------- ...BeatmapSearchMultipleSelectionFilterRow.cs | 83 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 74 +++++++++++++++++ .../Overlays/BeatmapListing/SearchExtra.cs | 4 +- 8 files changed, 179 insertions(+), 164 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs create mode 100644 osu.Game/Overlays/BeatmapListing/FilterTabItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 5c9431aad1..9b0ef4d6f2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 12383e7457..20bbd46529 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Overlays; @@ -21,7 +23,7 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } - public SearchExtra Extra { get; } + public List Extra { get; } public SearchPlayed Played { get; } @@ -40,7 +42,7 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - SearchExtra extra = SearchExtra.Any, + List extra = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); @@ -74,33 +76,14 @@ namespace osu.Game.Online.API.Requests req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); - req.AddCursor(cursor); - - if (Extra != SearchExtra.Any) - { - string extraString = string.Empty; - - switch (Extra) - { - case SearchExtra.Both: - extraString = "video.storyboard"; - break; - - case SearchExtra.Storyboard: - extraString = "storyboard"; - break; - - case SearchExtra.Video: - extraString = "video"; - break; - } - - req.AddParameter("e", extraString); - } + if (Extra != null && Extra.Any()) + req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); + req.AddCursor(cursor); + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 80beed6217..f390db3f35 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; +using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapListing { @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; - public Bindable Extra => extraFilter.Current; + public Bindable> Extra => extraFilter.Current; public Bindable Played => playedFilter.Current; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs index 6e81cd2976..385978096c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -1,94 +1,31 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osuTK; - namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapSearchExtraFilterRow : BeatmapSearchFilterRow + public class BeatmapSearchExtraFilterRow : BeatmapSearchMultipleSelectionFilterRow { public BeatmapSearchExtraFilterRow() : base("Extra") { } - protected override Drawable CreateFilter() => new ExtraFilter(); + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new ExtraFilter(); - private class ExtraFilter : FillFlowContainer, IHasCurrentValue + private class ExtraFilter : MultipleSelectionFilter { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current + protected override MultipleSelectionFilterTabItem[] CreateItems() => new[] { - get => current.Current; - set => current.Current = value; - } - - private readonly ExtraFilterTabItem videoItem; - private readonly ExtraFilterTabItem storyboardItem; - - public ExtraFilter() - { - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - RelativeSizeAxes = Axes.X; - Height = 15; - Spacing = new Vector2(10, 0); - AddRange(new[] - { - videoItem = new ExtraFilterTabItem(SearchExtra.Video), - storyboardItem = new ExtraFilterTabItem(SearchExtra.Storyboard) - }); - - foreach (var item in Children) - item.StateUpdated += updateBindable; - } - - private void updateBindable() - { - if (videoItem.Active.Value && storyboardItem.Active.Value) - { - Current.Value = SearchExtra.Both; - return; - } - - if (videoItem.Active.Value) - { - Current.Value = SearchExtra.Video; - return; - } - - if (storyboardItem.Active.Value) - { - Current.Value = SearchExtra.Storyboard; - return; - } - - Current.Value = SearchExtra.Any; - } + new ExtraFilterTabItem(SearchExtra.Video), + new ExtraFilterTabItem(SearchExtra.Storyboard) + }; } - private class ExtraFilterTabItem : FilterTabItem + private class ExtraFilterTabItem : MultipleSelectionFilterTabItem { - public event Action StateUpdated; - public ExtraFilterTabItem(SearchExtra value) : base(value) { - Active.BindValueChanged(_ => StateUpdated?.Invoke()); - } - - protected override bool OnClick(ClickEvent e) - { - base.OnClick(e); - Active.Value = !Active.Value; - return true; } protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index ad32475b25..aa0fc0d00e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -1,20 +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; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; using Humanizer; using osu.Game.Utils; @@ -98,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapListing protected override Dropdown CreateDropdown() => new FilterDropdown(); - protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); + protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); private class FilterDropdown : OsuTabDropdown { @@ -117,63 +113,5 @@ namespace osu.Game.Overlays.BeatmapListing } } } - - protected class FilterTabItem : TabItem - { - protected virtual float TextSize => 13; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private readonly OsuSpriteText text; - - public FilterTabItem(T value) - : base(value) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - AddRangeInternal(new Drawable[] - { - text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), - Text = CreateText(value) - }, - new HoverClickSounds() - }); - - Enabled.Value = true; - } - - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); - - [BackgroundDependencyLoader] - private void load() - { - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); - - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; - } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs new file mode 100644 index 0000000000..c434e00ff3 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Overlays.BeatmapListing +{ + public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> + { + public BeatmapSearchMultipleSelectionFilterRow(string headerName) + : base(headerName) + { + } + + protected override Drawable CreateFilter() => CreateMultipleSelectionFilter(); + + protected abstract MultipleSelectionFilter CreateMultipleSelectionFilter(); + + protected abstract class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + { + private readonly BindableWithCurrent> current = new BindableWithCurrent>(); + + public Bindable> Current + { + get => current.Current; + set => current.Current = value; + } + + public MultipleSelectionFilter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + Height = 15; + Spacing = new Vector2(10, 0); + AddRange(CreateItems()); + + foreach (var item in Children) + item.StateUpdated += updateBindable; + } + + protected abstract MultipleSelectionFilterTabItem[] CreateItems(); + + private void updateBindable() + { + var selectedValues = new List(); + + foreach (var item in Children) + { + if (item.Active.Value) + selectedValues.Add(item.Value); + } + + Current.Value = selectedValues; + } + } + + protected class MultipleSelectionFilterTabItem : FilterTabItem + { + public event Action StateUpdated; + + public MultipleSelectionFilterTabItem(T value) + : base(value) + { + Active.BindValueChanged(_ => StateUpdated?.Invoke()); + } + + protected override bool OnClick(ClickEvent e) + { + base.OnClick(e); + Active.Value = !Active.Value; + return true; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs new file mode 100644 index 0000000000..32b48862e0 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class FilterTabItem : TabItem + { + protected virtual float TextSize => 13; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly OsuSpriteText text; + + public FilterTabItem(T value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + AddRangeInternal(new Drawable[] + { + text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Text = CreateText(value) + }, + new HoverClickSounds() + }); + + Enabled.Value = true; + } + + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + + private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + } +} diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index 53900211e1..e9b3165d97 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -5,9 +5,7 @@ namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { - Any, Video, - Storyboard, - Both + Storyboard } } From 008d1d697cee99eee4e5cf076dcb209f71189a9f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 23:14:48 +0300 Subject: [PATCH 213/322] Implement filtering by rank achieved --- .../TestSceneBeatmapListingSearchControl.cs | 3 ++ .../API/Requests/SearchBeatmapSetsRequest.cs | 7 ++++ .../BeatmapListingFilterControl.cs | 2 ++ .../BeatmapListingSearchControl.cs | 4 +++ .../BeatmapSearchExtraFilterRow.cs | 2 +- ...BeatmapSearchMultipleSelectionFilterRow.cs | 4 +-- .../BeatmapSearchRankFilterRow.cs | 35 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 10 +++--- .../Overlays/BeatmapListing/SearchRank.cs | 24 +++++++++++++ 9 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs create mode 100644 osu.Game/Overlays/BeatmapListing/SearchRank.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 9b0ef4d6f2..ff8162293b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText genre; OsuSpriteText language; OsuSpriteText extra; + OsuSpriteText ranks; OsuSpriteText played; Add(control = new BeatmapListingSearchControl @@ -50,6 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface genre = new OsuSpriteText(), language = new OsuSpriteText(), extra = new OsuSpriteText(), + ranks = new OsuSpriteText(), played = new OsuSpriteText() } }); @@ -60,6 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); + control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join(".", r.NewValue.Select(i => i.ToString())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 20bbd46529..f819a5778f 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,6 +27,8 @@ namespace osu.Game.Online.API.Requests public SearchPlayed Played { get; } + public List Ranks { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -43,6 +45,7 @@ namespace osu.Game.Online.API.Requests SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, List extra = null, + List ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); @@ -55,6 +58,7 @@ namespace osu.Game.Online.API.Requests Genre = genre; Language = language; Extra = extra; + Ranks = ranks; Played = played; } @@ -79,6 +83,9 @@ namespace osu.Game.Online.API.Requests if (Extra != null && Extra.Any()) req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); + if (Ranks != null && Ranks.Any()) + req.AddParameter("r", string.Join(".", Ranks.Select(r => r.ToString()))); + if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 3f09a7e3d1..86bf3276fe 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -131,6 +131,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Ranks.BindValueChanged(_ => queueUpdateSearch()); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); @@ -183,6 +184,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.Value, searchControl.Language.Value, searchControl.Extra.Value, + searchControl.Ranks.Value, searchControl.Played.Value); getSetsRequest.Success += response => diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index f390db3f35..64bd7065b5 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable> Extra => extraFilter.Current; + public Bindable> Ranks => ranksFilter.Current; + public Bindable Played => playedFilter.Current; public BeatmapSetInfo BeatmapSet @@ -54,6 +56,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchExtraFilterRow extraFilter; + private readonly BeatmapSearchRankFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -113,6 +116,7 @@ namespace osu.Game.Overlays.BeatmapListing genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), extraFilter = new BeatmapSearchExtraFilterRow(), + ranksFilter = new BeatmapSearchRankFilterRow(), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs index 385978096c..d8bf18fb88 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing private class ExtraFilter : MultipleSelectionFilter { - protected override MultipleSelectionFilterTabItem[] CreateItems() => new[] + protected override MultipleSelectionFilterTabItem[] CreateItems() => new MultipleSelectionFilterTabItem[] { new ExtraFilterTabItem(SearchExtra.Video), new ExtraFilterTabItem(SearchExtra.Storyboard) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index c434e00ff3..acf2c62afa 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing { public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { - public BeatmapSearchMultipleSelectionFilterRow(string headerName) + protected BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { } @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - public MultipleSelectionFilter() + protected MultipleSelectionFilter() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs new file mode 100644 index 0000000000..d521e8c90f --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Extensions; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchRankFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchRankFilterRow() + : base("Rank Achieved") + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); + + private class RankFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem[] CreateItems() + => ((SearchRank[])Enum.GetValues(typeof(SearchRank))).Select(v => new RankFilterTabItem(v)).ToArray(); + } + + private class RankFilterTabItem : MultipleSelectionFilterTabItem + { + public RankFilterTabItem(SearchRank value) + : base(value) + { + } + + protected override string CreateText(SearchRank value) => $@"{value.GetDescription() ?? value.ToString()}"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 32b48862e0..9bdd5b3fad 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -16,8 +16,6 @@ namespace osu.Game.Overlays.BeatmapListing { public class FilterTabItem : TabItem { - protected virtual float TextSize => 13; - [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -33,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing { text = new OsuSpriteText { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), Text = CreateText(value) }, new HoverClickSounds() @@ -67,7 +65,11 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + private void updateState() + { + text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); + } private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; } diff --git a/osu.Game/Overlays/BeatmapListing/SearchRank.cs b/osu.Game/Overlays/BeatmapListing/SearchRank.cs new file mode 100644 index 0000000000..8b1882026c --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchRank.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchRank + { + [Description(@"Silver SS")] + XH, + + [Description(@"SS")] + X, + + [Description(@"Silver S")] + SH, + S, + A, + B, + C, + D + } +} From c4efceceb2a384af3468db13839c6668675038c3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 23:57:11 +0300 Subject: [PATCH 214/322] Use char instead of sting for request parameter creation --- .../UserInterface/TestSceneBeatmapListingSearchControl.cs | 4 ++-- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index ff8162293b..e07aa71b1f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); - control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join(".", r.NewValue.Select(i => i.ToString())))}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join('.', e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); + control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join('.', r.NewValue.Select(i => i.ToString())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index f819a5778f..248096d8b3 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -81,10 +81,10 @@ namespace osu.Game.Online.API.Requests req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); if (Extra != null && Extra.Any()) - req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); + req.AddParameter("e", string.Join('.', Extra.Select(e => e.ToString().ToLowerInvariant()))); if (Ranks != null && Ranks.Any()) - req.AddParameter("r", string.Join(".", Ranks.Select(r => r.ToString()))); + req.AddParameter("r", string.Join('.', Ranks.Select(r => r.ToString()))); if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); From b4ec3b9fefa6206558cf3f3e0ea6541cac0948e6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 01:41:46 +0300 Subject: [PATCH 215/322] Simplify MultipleSelectionFilterTabItem state changes --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index acf2c62afa..35c982d35a 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.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 osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapListing AddRange(CreateItems()); foreach (var item in Children) - item.StateUpdated += updateBindable; + item.Active.BindValueChanged(_ => updateBindable()); } protected abstract MultipleSelectionFilterTabItem[] CreateItems(); @@ -64,18 +63,15 @@ namespace osu.Game.Overlays.BeatmapListing protected class MultipleSelectionFilterTabItem : FilterTabItem { - public event Action StateUpdated; - public MultipleSelectionFilterTabItem(T value) : base(value) { - Active.BindValueChanged(_ => StateUpdated?.Invoke()); } protected override bool OnClick(ClickEvent e) { base.OnClick(e); - Active.Value = !Active.Value; + Active.Toggle(); return true; } } From fd11346a289e03b0a5f2f1f2cd5d1da3a578fffe Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 01:48:24 +0300 Subject: [PATCH 216/322] Update button colours --- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 9bdd5b3fad..721a3c839c 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -67,10 +67,10 @@ namespace osu.Game.Overlays.BeatmapListing private void updateState() { - text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); } - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + private Color4 getStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; } } From 03c5057a921d8ddf21666671c76e23bc396b8a60 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:28:31 +0300 Subject: [PATCH 217/322] Simplify BeatmapSearchMultipleSelectionFilterRow --- .../BeatmapListingSearchControl.cs | 8 ++--- .../BeatmapSearchExtraFilterRow.cs | 34 ------------------ ...BeatmapSearchMultipleSelectionFilterRow.cs | 21 ++++++----- .../BeatmapSearchRankFilterRow.cs | 35 ------------------- .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +-- .../Overlays/BeatmapListing/SearchExtra.cs | 4 +++ 6 files changed, 19 insertions(+), 87 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 64bd7065b5..a976890c7c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -55,8 +55,8 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; - private readonly BeatmapSearchExtraFilterRow extraFilter; - private readonly BeatmapSearchRankFilterRow ranksFilter; + private readonly BeatmapSearchMultipleSelectionFilterRow extraFilter; + private readonly BeatmapSearchMultipleSelectionFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -115,8 +115,8 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchExtraFilterRow(), - ranksFilter = new BeatmapSearchRankFilterRow(), + extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), + ranksFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Rank Achieved"), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs deleted file mode 100644 index d8bf18fb88..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchExtraFilterRow : BeatmapSearchMultipleSelectionFilterRow - { - public BeatmapSearchExtraFilterRow() - : base("Extra") - { - } - - protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new ExtraFilter(); - - private class ExtraFilter : MultipleSelectionFilter - { - protected override MultipleSelectionFilterTabItem[] CreateItems() => new MultipleSelectionFilterTabItem[] - { - new ExtraFilterTabItem(SearchExtra.Video), - new ExtraFilterTabItem(SearchExtra.Storyboard) - }; - } - - private class ExtraFilterTabItem : MultipleSelectionFilterTabItem - { - public ExtraFilterTabItem(SearchExtra value) - : base(value) - { - } - - protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 35c982d35a..cb89560e39 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.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; using System.Collections.Generic; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -11,18 +13,16 @@ using osuTK; namespace osu.Game.Overlays.BeatmapListing { - public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> + public class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { - protected BeatmapSearchMultipleSelectionFilterRow(string headerName) + public BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { } - protected override Drawable CreateFilter() => CreateMultipleSelectionFilter(); + protected override Drawable CreateFilter() => new MultipleSelectionFilter(); - protected abstract MultipleSelectionFilter CreateMultipleSelectionFilter(); - - protected abstract class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + private class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> { private readonly BindableWithCurrent> current = new BindableWithCurrent>(); @@ -32,21 +32,20 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - protected MultipleSelectionFilter() + public MultipleSelectionFilter() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.X; Height = 15; Spacing = new Vector2(10, 0); - AddRange(CreateItems()); + + ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); foreach (var item in Children) item.Active.BindValueChanged(_ => updateBindable()); } - protected abstract MultipleSelectionFilterTabItem[] CreateItems(); - private void updateBindable() { var selectedValues = new List(); @@ -61,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapListing } } - protected class MultipleSelectionFilterTabItem : FilterTabItem + private class MultipleSelectionFilterTabItem : FilterTabItem { public MultipleSelectionFilterTabItem(T value) : base(value) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs deleted file mode 100644 index d521e8c90f..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs +++ /dev/null @@ -1,35 +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.Linq; -using osu.Framework.Extensions; - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchRankFilterRow : BeatmapSearchMultipleSelectionFilterRow - { - public BeatmapSearchRankFilterRow() - : base("Rank Achieved") - { - } - - protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); - - private class RankFilter : MultipleSelectionFilter - { - protected override MultipleSelectionFilterTabItem[] CreateItems() - => ((SearchRank[])Enum.GetValues(typeof(SearchRank))).Select(v => new RankFilterTabItem(v)).ToArray(); - } - - private class RankFilterTabItem : MultipleSelectionFilterTabItem - { - public RankFilterTabItem(SearchRank value) - : base(value) - { - } - - protected override string CreateText(SearchRank value) => $@"{value.GetDescription() ?? value.ToString()}"; - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 721a3c839c..244ef5a703 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = CreateText(value) + Text = (value as Enum)?.GetDescription() ?? value.ToString() }, new HoverClickSounds() }); @@ -40,8 +40,6 @@ namespace osu.Game.Overlays.BeatmapListing Enabled.Value = true; } - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index e9b3165d97..0ee60c4a95 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.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.ComponentModel; + namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { + [Description("Has Video")] Video, + [Description("Has Storyboard")] Storyboard } } From 6fd3686c4d443ca4c4daad14e1f0885d7747b26f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:36:35 +0300 Subject: [PATCH 218/322] Use IReadOnlyCollection instead of List in SearchBeatmapSetsRequest --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 248096d8b3..708b58d954 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -23,11 +23,11 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } - public List Extra { get; } + public IReadOnlyCollection Extra { get; } public SearchPlayed Played { get; } - public List Ranks { get; } + public IReadOnlyCollection Ranks { get; } private readonly string query; private readonly RulesetInfo ruleset; @@ -44,8 +44,8 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - List extra = null, - List ranks = null, + IReadOnlyCollection extra = null, + IReadOnlyCollection ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); From 914bd537885c946c8bd5fda14aecde6c8b3bc90d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:39:51 +0300 Subject: [PATCH 219/322] Add missing blank line --- osu.Game/Overlays/BeatmapListing/SearchExtra.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index 0ee60c4a95..af37e3264f 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -9,6 +9,7 @@ namespace osu.Game.Overlays.BeatmapListing { [Description("Has Video")] Video, + [Description("Has Storyboard")] Storyboard } From 01b576c8611bfca9f3d2ba3bad5974ed06688759 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 13:32:39 +0900 Subject: [PATCH 220/322] Fix editor crash on exit when forcing exit twice in a row --- osu.Game/Screens/Edit/Editor.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c3560dff38..0aaa551af9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -444,12 +444,21 @@ namespace osu.Game.Screens.Edit if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog) { confirmExit(); - return true; + return false; } if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + dialogOverlay?.Push(new PromptForSaveDialog(() => + { + confirmExit(); + this.Exit(); + }, () => + { + confirmExitWithSave(); + this.Exit(); + })); + return true; } } @@ -464,7 +473,6 @@ namespace osu.Game.Screens.Edit { exitConfirmed = true; Save(); - this.Exit(); } private void confirmExit() @@ -483,7 +491,6 @@ namespace osu.Game.Screens.Edit } exitConfirmed = true; - this.Exit(); } private readonly Bindable clipboard = new Bindable(); From 9b9a41596f4f651d03321e42eef89609d48954c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 14:42:23 +0900 Subject: [PATCH 221/322] Split out frame stability calculation to own method --- .../Rulesets/UI/FrameStabilityContainer.cs | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 28b7975a89..94684f33ed 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -119,35 +119,19 @@ namespace osu.Game.Rulesets.UI if (parentGameplayClock == null) setClock(); // LoadComplete may not be run yet, but we still want the clock. + // each update start with considering things in valid state. validState = true; requireMoreUpdateLoops = false; - var newProposedTime = parentGameplayClock.CurrentTime; + // our goal is to catch up to the time provided by the parent clock. + var proposedTime = parentGameplayClock.CurrentTime; try { if (FrameStablePlayback) - { - if (firstConsumption) - { - // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. - // Instead we perform an initial seek to the proposed time. - - // process frame (in addition to finally clause) to clear out ElapsedTime - manualClock.CurrentTime = newProposedTime; - framedClock.ProcessFrame(); - - firstConsumption = false; - } - else if (manualClock.CurrentTime < gameplayStartTime) - manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); - else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) - { - newProposedTime = newProposedTime > manualClock.CurrentTime - ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); - } - } + // if we require frame stability, the proposed time will be adjusted to move at most one known + // frame interval in the current direction. + applyFrameStability(ref proposedTime); if (isAttached) { @@ -156,7 +140,7 @@ namespace osu.Game.Rulesets.UI if (FrameStablePlayback) { // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. - if ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) == null) + if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) { // setting invalid state here ensures that gameplay will not continue (ie. our child // hierarchy won't be updated). @@ -173,7 +157,7 @@ namespace osu.Game.Rulesets.UI // when stability is disabled, we don't really care about accuracy. // looping over the replay will allow it to catch up and feed out the required values // for the current time. - while ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) != newProposedTime) + while ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) != proposedTime) { if (newTime == null) { @@ -185,15 +169,15 @@ namespace osu.Game.Rulesets.UI } } - newProposedTime = newTime.Value; + proposedTime = newTime.Value; } } finally { - if (newProposedTime != manualClock.CurrentTime) - direction = newProposedTime > manualClock.CurrentTime ? 1 : -1; + if (proposedTime != manualClock.CurrentTime) + direction = proposedTime > manualClock.CurrentTime ? 1 : -1; - manualClock.CurrentTime = newProposedTime; + manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; manualClock.IsRunning = parentGameplayClock.IsRunning; @@ -209,6 +193,35 @@ namespace osu.Game.Rulesets.UI } } + /// + /// Apply frame stability modifier to a time. + /// + /// The time which is to be displayed. + private void applyFrameStability(ref double proposedTime) + { + if (firstConsumption) + { + // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. + // Instead we perform an initial seek to the proposed time. + + // process frame (in addition to finally clause) to clear out ElapsedTime + manualClock.CurrentTime = proposedTime; + framedClock.ProcessFrame(); + + firstConsumption = false; + return; + } + + if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = proposedTime = Math.Min(gameplayStartTime, proposedTime); + else if (Math.Abs(manualClock.CurrentTime - proposedTime) > sixty_frame_time * 1.2f) + { + proposedTime = proposedTime > manualClock.CurrentTime + ? Math.Min(proposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(proposedTime, manualClock.CurrentTime - sixty_frame_time); + } + } + private void setClock() { if (parentGameplayClock == null) From 8c9bda2ded2a973bd4896aa7795fb30830804af2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 14:53:31 +0900 Subject: [PATCH 222/322] Split out replay update method --- .../Rulesets/UI/FrameStabilityContainer.cs | 85 ++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 94684f33ed..7f27b283e3 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.UI protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - private bool isAttached => ReplayInputHandler != null; + private bool hasReplayAttached => ReplayInputHandler != null; private const double sixty_frame_time = 1000.0 / 60; @@ -133,44 +133,8 @@ namespace osu.Game.Rulesets.UI // frame interval in the current direction. applyFrameStability(ref proposedTime); - if (isAttached) - { - double? newTime; - - if (FrameStablePlayback) - { - // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. - if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) - { - // setting invalid state here ensures that gameplay will not continue (ie. our child - // hierarchy won't be updated). - validState = false; - - // potentially loop to catch-up playback. - requireMoreUpdateLoops = true; - - return; - } - } - else - { - // when stability is disabled, we don't really care about accuracy. - // looping over the replay will allow it to catch up and feed out the required values - // for the current time. - while ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) != proposedTime) - { - if (newTime == null) - { - // special case for when the replay actually can't arrive at the required time. - // protects from potential endless loop. - validState = false; - return; - } - } - } - - proposedTime = newTime.Value; - } + if (hasReplayAttached) + updateReplay(ref proposedTime); } finally { @@ -193,6 +157,49 @@ namespace osu.Game.Rulesets.UI } } + /// + /// Attempt to advance replay playback for a given time. + /// + /// The time which is to be displayed. + private bool updateReplay(ref double proposedTime) + { + double? newTime; + + if (FrameStablePlayback) + { + // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. + if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) + { + // setting invalid state here ensures that gameplay will not continue (ie. our child + // hierarchy won't be updated). + validState = false; + + // potentially loop to catch-up playback. + requireMoreUpdateLoops = true; + + return false; + } + } + else + { + // when stability is disabled, we don't really care about accuracy. + // looping over the replay will allow it to catch up and feed out the required values + // for the current time. + while ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) != proposedTime) + { + if (newTime == null) + { + // special case for when the replay actually can't arrive at the required time. + // protects from potential endless loop. + return false; + } + } + } + + proposedTime = newTime.Value; + return true; + } + /// /// Apply frame stability modifier to a time. /// From a06516c9004ad7273ace9ce161d9851ec1b055d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:11:53 +0900 Subject: [PATCH 223/322] Extract out frame stability state into enum for (hopefully) better clarity --- .../Rulesets/UI/FrameStabilityContainer.cs | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 7f27b283e3..12e4dd8b01 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -73,19 +73,9 @@ namespace osu.Game.Rulesets.UI setClock(); } - /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. - /// - private bool requireMoreUpdateLoops; + private PlaybackState state; - /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. - /// - private bool validState; - - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && state != PlaybackState.NotValid; private bool hasReplayAttached => ReplayInputHandler != null; @@ -95,20 +85,19 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { - requireMoreUpdateLoops = true; - validState = !frameStableClock.IsPaused.Value; + state = frameStableClock.IsPaused.Value ? PlaybackState.NotValid : PlaybackState.Valid; - int loops = 0; + int loops = MaxCatchUpFrames; - while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) + while (state != PlaybackState.NotValid && loops-- > 0) { updateClock(); - if (validState) - { - base.UpdateSubTree(); - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - } + if (state == PlaybackState.NotValid) + break; + + base.UpdateSubTree(); + UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); } return true; @@ -120,8 +109,7 @@ namespace osu.Game.Rulesets.UI setClock(); // LoadComplete may not be run yet, but we still want the clock. // each update start with considering things in valid state. - validState = true; - requireMoreUpdateLoops = false; + state = PlaybackState.Valid; // our goal is to catch up to the time provided by the parent clock. var proposedTime = parentGameplayClock.CurrentTime; @@ -134,7 +122,7 @@ namespace osu.Game.Rulesets.UI applyFrameStability(ref proposedTime); if (hasReplayAttached) - updateReplay(ref proposedTime); + state = updateReplay(ref proposedTime); } finally { @@ -147,7 +135,9 @@ namespace osu.Game.Rulesets.UI double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); - requireMoreUpdateLoops |= timeBehind != 0; + // determine whether catch-up is required. + if (state != PlaybackState.NotValid) + state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; frameStableClock.IsCatchingUp.Value = timeBehind > 200; @@ -161,24 +151,14 @@ namespace osu.Game.Rulesets.UI /// Attempt to advance replay playback for a given time. /// /// The time which is to be displayed. - private bool updateReplay(ref double proposedTime) + private PlaybackState updateReplay(ref double proposedTime) { double? newTime; if (FrameStablePlayback) { // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. - if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) - { - // setting invalid state here ensures that gameplay will not continue (ie. our child - // hierarchy won't be updated). - validState = false; - - // potentially loop to catch-up playback. - requireMoreUpdateLoops = true; - - return false; - } + newTime = ReplayInputHandler.SetFrameFromTime(proposedTime); } else { @@ -191,13 +171,16 @@ namespace osu.Game.Rulesets.UI { // special case for when the replay actually can't arrive at the required time. // protects from potential endless loop. - return false; + break; } } } + if (newTime == null) + return PlaybackState.NotValid; + proposedTime = newTime.Value; - return true; + return PlaybackState.Valid; } /// @@ -244,6 +227,26 @@ namespace osu.Game.Rulesets.UI public ReplayInputHandler ReplayInputHandler { get; set; } + private enum PlaybackState + { + /// + /// Playback is not possible. Child hierarchy should not be processed. + /// + NotValid, + + /// + /// Whether we are running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + RequiresCatchUp, + + /// + /// Whether we are in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + Valid + } + private class FrameStabilityClock : GameplayClock, IFrameStableClock { public GameplayClock ParentGameplayClock; From 59e9c2639ad503eff93b7d848de21c12abfecfeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:12:39 +0900 Subject: [PATCH 224/322] Remove try-finally --- .../Rulesets/UI/FrameStabilityContainer.cs | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 12e4dd8b01..4386acfcce 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -114,37 +114,32 @@ namespace osu.Game.Rulesets.UI // our goal is to catch up to the time provided by the parent clock. var proposedTime = parentGameplayClock.CurrentTime; - try - { - if (FrameStablePlayback) - // if we require frame stability, the proposed time will be adjusted to move at most one known - // frame interval in the current direction. - applyFrameStability(ref proposedTime); + if (FrameStablePlayback) + // if we require frame stability, the proposed time will be adjusted to move at most one known + // frame interval in the current direction. + applyFrameStability(ref proposedTime); - if (hasReplayAttached) - state = updateReplay(ref proposedTime); - } - finally - { - if (proposedTime != manualClock.CurrentTime) - direction = proposedTime > manualClock.CurrentTime ? 1 : -1; + if (hasReplayAttached) + state = updateReplay(ref proposedTime); - manualClock.CurrentTime = proposedTime; - manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; - manualClock.IsRunning = parentGameplayClock.IsRunning; + if (proposedTime != manualClock.CurrentTime) + direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; - double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); + manualClock.CurrentTime = proposedTime; + manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; + manualClock.IsRunning = parentGameplayClock.IsRunning; - // determine whether catch-up is required. - if (state != PlaybackState.NotValid) - state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; + double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); - frameStableClock.IsCatchingUp.Value = timeBehind > 200; + // determine whether catch-up is required. + if (state != PlaybackState.NotValid) + state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; - // The manual clock time has changed in the above code. The framed clock now needs to be updated - // to ensure that the its time is valid for our children before input is processed - framedClock.ProcessFrame(); - } + frameStableClock.IsCatchingUp.Value = timeBehind > 200; + + // The manual clock time has changed in the above code. The framed clock now needs to be updated + // to ensure that the its time is valid for our children before input is processed + framedClock.ProcessFrame(); } /// From c9515653b303a8fc3ec8753aae6c7114f86f94fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:31:57 +0900 Subject: [PATCH 225/322] Restore previous directionality logic to avoid logic differences --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4386acfcce..7e17c93bed 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.UI state = updateReplay(ref proposedTime); if (proposedTime != manualClock.CurrentTime) - direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; + direction = proposedTime > manualClock.CurrentTime ? 1 : -1; manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; From 2b1e79a4e8f324e4d0dcfa66c94b6db8112112d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:32:20 +0900 Subject: [PATCH 226/322] Simplify state changes further --- .../Rulesets/UI/FrameStabilityContainer.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 7e17c93bed..6548bee4ef 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -120,7 +120,12 @@ namespace osu.Game.Rulesets.UI applyFrameStability(ref proposedTime); if (hasReplayAttached) - state = updateReplay(ref proposedTime); + { + bool valid = updateReplay(ref proposedTime); + + if (!valid) + state = PlaybackState.NotValid; + } if (proposedTime != manualClock.CurrentTime) direction = proposedTime > manualClock.CurrentTime ? 1 : -1; @@ -132,8 +137,8 @@ namespace osu.Game.Rulesets.UI double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); // determine whether catch-up is required. - if (state != PlaybackState.NotValid) - state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; + if (state == PlaybackState.Valid && timeBehind > 0) + state = PlaybackState.RequiresCatchUp; frameStableClock.IsCatchingUp.Value = timeBehind > 200; @@ -146,7 +151,8 @@ namespace osu.Game.Rulesets.UI /// Attempt to advance replay playback for a given time. /// /// The time which is to be displayed. - private PlaybackState updateReplay(ref double proposedTime) + /// Whether playback is still valid. + private bool updateReplay(ref double proposedTime) { double? newTime; @@ -172,10 +178,10 @@ namespace osu.Game.Rulesets.UI } if (newTime == null) - return PlaybackState.NotValid; + return false; proposedTime = newTime.Value; - return PlaybackState.Valid; + return true; } /// From 4f6081c7f3f48fcedc899665a32e89b4a757e45c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 19:44:13 +0300 Subject: [PATCH 227/322] Use BindableList --- .../TestSceneBeatmapListingSearchControl.cs | 4 +-- .../BeatmapListingFilterControl.cs | 8 ++--- .../BeatmapListingSearchControl.cs | 5 ++- .../BeatmapListing/BeatmapSearchFilterRow.cs | 2 +- ...BeatmapSearchMultipleSelectionFilterRow.cs | 35 ++++++++----------- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index e07aa71b1f..3f757031f8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join('.', e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); - control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join('.', r.NewValue.Select(i => i.ToString())))}", true); + control.Extra.BindCollectionChanged((u, v) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); + control.Ranks.BindCollectionChanged((u, v) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 86bf3276fe..71f0d8c522 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,8 +130,8 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Ranks.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Extra.CollectionChanged += (u, v) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (u, v) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); @@ -183,8 +183,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.SortDirection.Value, searchControl.Genre.Value, searchControl.Language.Value, - searchControl.Extra.Value, - searchControl.Ranks.Value, + searchControl.Extra, + searchControl.Ranks, searchControl.Played.Value); getSetsRequest.Success += response => diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index a976890c7c..4fc5c5315b 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; -using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapListing { @@ -29,9 +28,9 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; - public Bindable> Extra => extraFilter.Current; + public BindableList Extra => extraFilter.Current; - public Bindable> Ranks => ranksFilter.Current; + public BindableList Ranks => ranksFilter.Current; public Bindable Played => playedFilter.Current; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index aa0fc0d00e..b429a5277b 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.BeatmapListing }); if (filter is IHasCurrentValue filterWithValue) - filterWithValue.Current = current; + Current = filterWithValue.Current; } [NotNull] diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index cb89560e39..993b475f32 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osuTK; @@ -15,22 +14,21 @@ namespace osu.Game.Overlays.BeatmapListing { public class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { + public new readonly BindableList Current = new BindableList(); + + private MultipleSelectionFilter filter; + public BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { + Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => new MultipleSelectionFilter(); + protected override Drawable CreateFilter() => filter = new MultipleSelectionFilter(); - private class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + private class MultipleSelectionFilter : FillFlowContainer { - private readonly BindableWithCurrent> current = new BindableWithCurrent>(); - - public Bindable> Current - { - get => current.Current; - set => current.Current = value; - } + public readonly BindableList Current = new BindableList(); public MultipleSelectionFilter() { @@ -43,20 +41,15 @@ namespace osu.Game.Overlays.BeatmapListing ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); foreach (var item in Children) - item.Active.BindValueChanged(_ => updateBindable()); + item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); } - private void updateBindable() + private void updateBindable(T value, bool active) { - var selectedValues = new List(); - - foreach (var item in Children) - { - if (item.Active.Value) - selectedValues.Add(item.Value); - } - - Current.Value = selectedValues; + if (active) + Current.Add(value); + else + Current.Remove(value); } } From 5c2c5f200075a4f7f19a089a9db3775a11b73668 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 23:35:08 +0300 Subject: [PATCH 228/322] Use existing ScoreRank for rank filter --- .../API/Requests/SearchBeatmapSetsRequest.cs | 5 +- .../BeatmapListingSearchControl.cs | 7 +-- ...BeatmapSearchMultipleSelectionFilterRow.cs | 14 ++++-- .../BeatmapSearchScoreFilterRow.cs | 48 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +- .../Overlays/BeatmapListing/SearchRank.cs | 24 ---------- 6 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs delete mode 100644 osu.Game/Overlays/BeatmapListing/SearchRank.cs diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 708b58d954..ed67c5f5ca 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -8,6 +8,7 @@ using osu.Game.Extensions; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests { @@ -27,7 +28,7 @@ namespace osu.Game.Online.API.Requests public SearchPlayed Played { get; } - public IReadOnlyCollection Ranks { get; } + public IReadOnlyCollection Ranks { get; } private readonly string query; private readonly RulesetInfo ruleset; @@ -45,7 +46,7 @@ namespace osu.Game.Online.API.Requests SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, IReadOnlyCollection extra = null, - IReadOnlyCollection ranks = null, + IReadOnlyCollection ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 4fc5c5315b..3694c9855e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing { @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing public BindableList Extra => extraFilter.Current; - public BindableList Ranks => ranksFilter.Current; + public BindableList Ranks => ranksFilter.Current; public Bindable Played => playedFilter.Current; @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchMultipleSelectionFilterRow extraFilter; - private readonly BeatmapSearchMultipleSelectionFilterRow ranksFilter; + private readonly BeatmapSearchScoreFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -115,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapListing genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), - ranksFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Rank Achieved"), + ranksFilter = new BeatmapSearchScoreFilterRow(), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 993b475f32..87e60c5bdd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -24,9 +24,11 @@ namespace osu.Game.Overlays.BeatmapListing Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => filter = new MultipleSelectionFilter(); + protected override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); - private class MultipleSelectionFilter : FillFlowContainer + protected virtual MultipleSelectionFilter CreateMultipleSelectionFilter() => new MultipleSelectionFilter(); + + protected class MultipleSelectionFilter : FillFlowContainer { public readonly BindableList Current = new BindableList(); @@ -38,12 +40,16 @@ namespace osu.Game.Overlays.BeatmapListing Height = 15; Spacing = new Vector2(10, 0); - ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); + GetValues().ForEach(i => Add(CreateTabItem(i))); foreach (var item in Children) item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); } + protected virtual T[] GetValues() => (T[])Enum.GetValues(typeof(T)); + + protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); + private void updateBindable(T value, bool active) { if (active) @@ -53,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapListing } } - private class MultipleSelectionFilterTabItem : FilterTabItem + protected class MultipleSelectionFilterTabItem : FilterTabItem { public MultipleSelectionFilterTabItem(T value) : base(value) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs new file mode 100644 index 0000000000..f741850f07 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -0,0 +1,48 @@ +// 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.Scoring; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchScoreFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchScoreFilterRow() + : base(@"Rank Achieved") + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); + + private class RankFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); + + protected override ScoreRank[] GetValues() => base.GetValues().Reverse().ToArray(); + } + + private class RankItem : MultipleSelectionFilterTabItem + { + public RankItem(ScoreRank value) + : base(value) + { + } + + protected override string CreateText(ScoreRank value) + { + switch (value) + { + case ScoreRank.XH: + return @"Silver SS"; + + case ScoreRank.SH: + return @"Silver S"; + + default: + return base.CreateText(value); + } + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 244ef5a703..c45a82bef1 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = (value as Enum)?.GetDescription() ?? value.ToString() + Text = CreateText(value) }, new HoverClickSounds() }); @@ -63,6 +63,8 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + private void updateState() { text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); diff --git a/osu.Game/Overlays/BeatmapListing/SearchRank.cs b/osu.Game/Overlays/BeatmapListing/SearchRank.cs deleted file mode 100644 index 8b1882026c..0000000000 --- a/osu.Game/Overlays/BeatmapListing/SearchRank.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 System.ComponentModel; - -namespace osu.Game.Overlays.BeatmapListing -{ - public enum SearchRank - { - [Description(@"Silver SS")] - XH, - - [Description(@"SS")] - X, - - [Description(@"Silver S")] - SH, - S, - A, - B, - C, - D - } -} From 202fe093065ef46560555d78402fbf3c49f8a762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:03:59 +0100 Subject: [PATCH 229/322] Group selection actions back up in SelectionHandler --- .../Sliders/SliderSelectionBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 ----------------- .../Compose/Components/BlueprintContainer.cs | 7 ------- .../Edit/Compose/Components/SelectionHandler.cs | 5 ++++- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ca9ec886d5..d3fb5defae 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -107,14 +107,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case MouseButton.Right: rightClickPosition = e.MouseDownPosition; - break; + return false; // Allow right click to be handled by context menu case MouseButton.Left when e.ControlPressed && IsSelected: placementControlPointIndex = addControlPoint(e.MousePosition); return true; // Stop input from being handled and modifying the selection } - return base.OnMouseDown(e); + return false; } private int? placementControlPointIndex; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 87ef7e647f..f3816f6218 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,13 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Screens.Edit; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -55,20 +52,6 @@ namespace osu.Game.Rulesets.Edit updateState(); } - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.CurrentState.Keyboard.ShiftPressed && e.IsPressed(MouseButton.Right)) - { - editorBeatmap.Remove(HitObject); - return true; - } - - return base.OnMouseDown(e); - } - private SelectionState state; public event Action StateChanged; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7751df29cf..5ac360d029 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -298,13 +298,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(!clickSelectionBegan); - // Deselections are only allowed for control + left clicks - bool allowDeselection = e.ControlPressed && e.Button == MouseButton.Left; - - // Todo: This is probably incorrectly disallowing multiple selections on stacked objects - if (!allowDeselection && SelectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) - return; - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (blueprint.IsHovered) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 24f88bf36d..01e23bafc5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -224,7 +225,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - if (state.Keyboard.ControlPressed) + if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) + EditorBeatmap.Remove(blueprint.HitObject); + else if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left)) blueprint.ToggleSelection(); else ensureSelected(blueprint); From fa53549ed271a7ff3486ff77835c93baa03ec48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:57:03 +0100 Subject: [PATCH 230/322] Mark request fields as possibly-null --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index ed67c5f5ca..bbaa7e745f 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Overlays; @@ -24,10 +25,12 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } + [CanBeNull] public IReadOnlyCollection Extra { get; } public SearchPlayed Played { get; } + [CanBeNull] public IReadOnlyCollection Ranks { get; } private readonly string query; From e77049eae39afb8cf363197079ccd9a4257c07b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:58:51 +0100 Subject: [PATCH 231/322] Use discard-like lambda parameter names --- .../Overlays/BeatmapListing/BeatmapListingFilterControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 71f0d8c522..3be38e3c1d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,8 +130,8 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.CollectionChanged += (u, v) => queueUpdateSearch(); - searchControl.Ranks.CollectionChanged += (u, v) => queueUpdateSearch(); + searchControl.Extra.CollectionChanged += (_, __) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (_, __) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); From f5aedc96c4244974750c651a94976f17ed283ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:07:54 +0100 Subject: [PATCH 232/322] Rework multiple selection filter --- ...BeatmapSearchMultipleSelectionFilterRow.cs | 23 ++++++++++++------- .../BeatmapSearchScoreFilterRow.cs | 3 ++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 87e60c5bdd..ae669e91dd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -32,7 +33,8 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly BindableList Current = new BindableList(); - public MultipleSelectionFilter() + [BackgroundDependencyLoader] + private void load() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; @@ -40,17 +42,22 @@ namespace osu.Game.Overlays.BeatmapListing Height = 15; Spacing = new Vector2(10, 0); - GetValues().ForEach(i => Add(CreateTabItem(i))); - - foreach (var item in Children) - item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); + AddRange(GetValues().Select(CreateTabItem)); } - protected virtual T[] GetValues() => (T[])Enum.GetValues(typeof(T)); + protected override void LoadComplete() + { + base.LoadComplete(); + + foreach (var item in Children) + item.Active.BindValueChanged(active => toggleItem(item.Value, active.NewValue)); + } + + protected virtual IEnumerable GetValues() => Enum.GetValues(typeof(T)).Cast(); protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); - private void updateBindable(T value, bool active) + private void toggleItem(T value, bool active) { if (active) Current.Add(value); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index f741850f07..dc642cb4cd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.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.Collections.Generic; using System.Linq; using osu.Game.Scoring; @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.BeatmapListing { protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); - protected override ScoreRank[] GetValues() => base.GetValues().Reverse().ToArray(); + protected override IEnumerable GetValues() => base.GetValues().Reverse(); } private class RankItem : MultipleSelectionFilterTabItem From a8cefb0d4ce014b5944559188d2faf063fdfcf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:12:28 +0100 Subject: [PATCH 233/322] Rename method --- .../Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs | 5 +++-- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index dc642cb4cd..804962adfb 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override string CreateText(ScoreRank value) + protected override string LabelFor(ScoreRank value) { switch (value) { @@ -41,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapListing return @"Silver S"; default: - return base.CreateText(value); + return value.GetDescription(); } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index c45a82bef1..43913c7ce2 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = CreateText(value) + Text = LabelFor(value) }, new HoverClickSounds() }); @@ -63,7 +63,10 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + /// + /// Returns the label text to be used for the supplied . + /// + protected virtual string LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); private void updateState() { From 016e920aa9fff8a7b4c2aaf4236a70cfff5b3038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:14:52 +0100 Subject: [PATCH 234/322] Move filter tab item hierarchy construction to BDL --- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 43913c7ce2..f02b515755 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -19,10 +19,15 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OverlayColourProvider colourProvider { get; set; } - private readonly OsuSpriteText text; + private OsuSpriteText text; public FilterTabItem(T value) : base(value) + { + } + + [BackgroundDependencyLoader] + private void load() { AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomLeft; @@ -32,17 +37,12 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = LabelFor(value) + Text = LabelFor(Value) }, new HoverClickSounds() }); Enabled.Value = true; - } - - [BackgroundDependencyLoader] - private void load() - { updateState(); } From 1313ab89e76d0a0f5caaed1274c6a12971a34db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:37:21 +0100 Subject: [PATCH 235/322] Add xmldoc to multiple selection row --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index ae669e91dd..5dfa8e6109 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -25,8 +25,11 @@ namespace osu.Game.Overlays.BeatmapListing Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); + protected sealed override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); + /// + /// Creates a filter control that can be used to simultaneously select multiple values of type . + /// protected virtual MultipleSelectionFilter CreateMultipleSelectionFilter() => new MultipleSelectionFilter(); protected class MultipleSelectionFilter : FillFlowContainer @@ -53,8 +56,14 @@ namespace osu.Game.Overlays.BeatmapListing item.Active.BindValueChanged(active => toggleItem(item.Value, active.NewValue)); } + /// + /// Returns all values to be displayed in this filter row. + /// protected virtual IEnumerable GetValues() => Enum.GetValues(typeof(T)).Cast(); + /// + /// Creates a representing the supplied . + /// protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); private void toggleItem(T value, bool active) From 2e5a8b2287ce1b74f316671d9533faf87cb967f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 13:16:27 +0900 Subject: [PATCH 236/322] Fix xmldoc to read better in new context --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 6548bee4ef..595574115c 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -236,14 +236,13 @@ namespace osu.Game.Rulesets.UI NotValid, /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. + /// Playback is running behind real-time. Catch-up will be attempted by processing more than once per + /// game loop (limited to a sane maximum to avoid frame drops). /// RequiresCatchUp, /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// In a valid state, progressing one child hierarchy loop per game loop. /// Valid } From c0960e60cb59a50ace68901ae8a71df26ebc0e7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 14:52:34 +0900 Subject: [PATCH 237/322] Add note about testflight link Sick of getting asked about this. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7c749f3422..86c42dae12 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ If you are looking to install or test osu! without setting up a development envi | [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | +- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. + - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. From 3751c357a35b82bbc90cefe72eefe1052582f466 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:19:05 +0900 Subject: [PATCH 238/322] 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 2d531cf01e..27846fdf53 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 ca588b89d9..609ac0e5f9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9c22dec330..ebd38bc334 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 3ea27e23e8b8395b091221a78700f9c4b95aa347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:20:10 +0900 Subject: [PATCH 239/322] Update namespace references --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 + osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index fde42bec04..9bfb6aa839 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 9a0217a1eb..4cadfa9ad4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From a8e9c62583c8951b4e2f86b449fb1da6f75f3433 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:11:25 +0900 Subject: [PATCH 240/322] Make results panels aware of whether they are a local score that has just been set --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 2 +- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 7 ++++++- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 8 ++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ScorePanel.cs | 7 +++++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 5 +++-- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 1e87893f39..f69ccc1773 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) + new AccuracyCircle(score, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 250fdc5ebd..5af55e99f8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Ranking private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { - Child = panel = new ScorePanel(score) + Child = panel = new ScorePanel(score, true) { 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 45da23f1f9..9aeb2b60b7 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -73,14 +73,19 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private readonly ScoreInfo score; + private readonly bool withFlair; + private SmoothCircularProgress accuracyCircle; private SmoothCircularProgress innerMask; private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score) + private SampleChannel applauseSound; + + public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; + this.withFlair = withFlair; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 30747438c3..5f8609d190 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Ranking.Expanded private const float padding = 10; private readonly ScoreInfo score; + private readonly bool withFlair; + private readonly List statisticDisplays = new List(); private FillFlowContainer starAndModDisplay; @@ -41,9 +43,11 @@ namespace osu.Game.Screens.Ranking.Expanded /// Creates a new . /// /// The score to display. - public ExpandedPanelMiddleContent(ScoreInfo score) + /// Whether to add flair for a new score being set. + public ExpandedPanelMiddleContent(ScoreInfo score, bool withFlair = false) { this.score = score; + this.withFlair = withFlair; RelativeSizeAxes = Axes.Both; Masking = true; @@ -116,7 +120,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score) + Child = new AccuracyCircle(score, withFlair) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 026ce01857..f8bdf0140c 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - ScorePanelList.AddScore(Score); + ScorePanelList.AddScore(Score, true); if (player != null && allowRetry) { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index ee97ee55eb..6e6227da38 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,6 +85,8 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; + private readonly bool isNewLocalScore; + private Container content; private Container topLayerContainer; @@ -97,9 +99,10 @@ namespace osu.Game.Screens.Ranking private Container middleLayerContentContainer; private Drawable middleLayerContent; - public ScorePanel(ScoreInfo score) + public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; + this.isNewLocalScore = isNewLocalScore; } [BackgroundDependencyLoader] @@ -209,7 +212,7 @@ namespace osu.Game.Screens.Ranking 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).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, isNewLocalScore).With(d => d.Alpha = 0)); break; case PanelState.Contracted: diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0d7d339df0..cc163ba762 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -95,9 +95,10 @@ namespace osu.Game.Screens.Ranking /// Adds a to this list. /// /// The to add. - public ScorePanel AddScore(ScoreInfo score) + /// Whether this is a score that has just been achieved locally. Controls whether flair is added to the display or not. + public ScorePanel AddScore(ScoreInfo score, bool isNewLocalScore = false) { - var panel = new ScorePanel(score) + var panel = new ScorePanel(score, isNewLocalScore) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From fb82c043a515cb95c3267eacdd915be43ff4e3c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:11:37 +0900 Subject: [PATCH 241/322] Add rank appear sound (new default) --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 9aeb2b60b7..0c15aa509f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -4,6 +4,8 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -89,8 +91,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + if (withFlair) + applauseSound = audio.Samples.Get(score.Rank >= ScoreRank.A ? "Results/rankpass" : "Results/rankfail"); + InternalChildren = new Drawable[] { new SmoothCircularProgress @@ -239,11 +244,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy continue; using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true)) + { badge.Appear(); + } } using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true)) + { + this.Delay(-1440).Schedule(() => applauseSound?.Play()); rankText.Appear(); + } } } From b49a57941103145f782810e873b811723e593139 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:32:03 +0900 Subject: [PATCH 242/322] Allow SampleInfo to specify fallback sample lookup names --- osu.Game/Audio/SampleInfo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 2406b0bef2..240d70c418 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -10,14 +10,14 @@ namespace osu.Game.Audio /// public class SampleInfo : ISampleInfo { - private readonly string sampleName; + private readonly string[] sampleNames; - public SampleInfo(string sampleName) + public SampleInfo(params string[] sampleNames) { - this.sampleName = sampleName; + this.sampleNames = sampleNames; } - public IEnumerable LookupNames => new[] { sampleName }; + public IEnumerable LookupNames => sampleNames; public int Volume { get; } = 100; } From c863341ca1e7f1965222cbc0db0471da9c61ca29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:32:20 +0900 Subject: [PATCH 243/322] Don't force Gameplay prefix on all skin sample lookups --- osu.Game/Audio/HitSampleInfo.cs | 4 ++-- osu.Game/Skinning/SkinnableSound.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 8b1f5a366a..8efaeb3795 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -50,9 +50,9 @@ namespace osu.Game.Audio get { if (!string.IsNullOrEmpty(Suffix)) - yield return $"{Bank}-{Name}{Suffix}"; + yield return $"Gameplay/{Bank}-{Name}{Suffix}"; - yield return $"{Bank}-{Name}"; + yield return $"Gameplay/{Bank}-{Name}"; } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f6e91811dd..ffa0a963ce 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning { foreach (var lookup in s.LookupNames) { - if ((ch = samples.Get($"Gameplay/{lookup}")) != null) + if ((ch = samples.Get(lookup)) != null) break; } } From 5d5b0221e5199f96e0fda82669862bbe8854ec72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:32:29 +0900 Subject: [PATCH 244/322] Add skinning support for legacy applause playback --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 0c15aa509f..c6d4b66724 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -11,9 +11,11 @@ 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 @@ -82,7 +84,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - private SampleChannel applauseSound; + private SkinnableSound applauseSound; public AccuracyCircle(ScoreInfo score, bool withFlair) { @@ -93,9 +95,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy [BackgroundDependencyLoader] private void load(AudioManager audio) { - if (withFlair) - applauseSound = audio.Samples.Get(score.Rank >= ScoreRank.A ? "Results/rankpass" : "Results/rankfail"); - InternalChildren = new Drawable[] { new SmoothCircularProgress @@ -213,6 +212,13 @@ 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) From f1ce09930eb0fc5ff86b164a01c6edd2b6183894 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:03:45 +0900 Subject: [PATCH 245/322] Fix panel expanded state being updated multiple times unnecessarily --- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index cc163ba762..e85580a734 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -119,7 +119,10 @@ namespace osu.Game.Screens.Ranking })); if (SelectedScore.Value == score) - selectedScoreChanged(new ValueChangedEvent(SelectedScore.Value, SelectedScore.Value)); + { + if (IsLoaded) + SelectedScore.TriggerChange(); + } else { // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. @@ -143,11 +146,15 @@ namespace osu.Game.Screens.Ranking /// The to present. private void selectedScoreChanged(ValueChangedEvent score) { - // Contract the old panel. - foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + // avoid contracting panels unnecessarily when TriggerChange is fired manually. + if (score.OldValue != score.NewValue) { - t.Panel.State = PanelState.Contracted; - t.Margin = new MarginPadding(); + // Contract the old panel. + foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + { + t.Panel.State = PanelState.Contracted; + t.Margin = new MarginPadding(); + } } // Find the panel corresponding to the new score. From 4a26084df838916ae5cac8124c12d08aba74a106 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:04:33 +0900 Subject: [PATCH 246/322] Only play results panel animation once (and only for the local user) --- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 3 +++ osu.Game/Screens/Ranking/ScorePanel.cs | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5f8609d190..711763330c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -270,6 +270,9 @@ namespace osu.Game.Screens.Ranking.Expanded delay += 200; } } + + if (!withFlair) + FinishTransforms(true); }); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 6e6227da38..df710e4eb8 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - private readonly bool isNewLocalScore; + private bool displayWithFlair; private Container content; @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Ranking public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; - this.isNewLocalScore = isNewLocalScore; + displayWithFlair = isNewLocalScore; } [BackgroundDependencyLoader] @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Ranking state = value; - if (LoadState >= LoadState.Ready) + if (IsLoaded) updateState(); StateChanged?.Invoke(value); @@ -212,7 +212,10 @@ namespace osu.Game.Screens.Ranking 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, isNewLocalScore).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); + + // only the first expanded display should happen with flair. + displayWithFlair = false; break; case PanelState.Contracted: From 71e373ff511de83b10282992570165f087741551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:11:25 +0900 Subject: [PATCH 247/322] Make results panels aware of whether they are a local score that has just been set --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 2 +- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 8 ++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ScorePanel.cs | 7 +++++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 5 +++-- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 1e87893f39..f69ccc1773 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) + new AccuracyCircle(score, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 250fdc5ebd..5af55e99f8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Ranking private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { - Child = panel = new ScorePanel(score) + Child = panel = new ScorePanel(score, true) { 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 45da23f1f9..337665b51f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score) + public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 30747438c3..5f8609d190 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Ranking.Expanded private const float padding = 10; private readonly ScoreInfo score; + private readonly bool withFlair; + private readonly List statisticDisplays = new List(); private FillFlowContainer starAndModDisplay; @@ -41,9 +43,11 @@ namespace osu.Game.Screens.Ranking.Expanded /// Creates a new . /// /// The score to display. - public ExpandedPanelMiddleContent(ScoreInfo score) + /// Whether to add flair for a new score being set. + public ExpandedPanelMiddleContent(ScoreInfo score, bool withFlair = false) { this.score = score; + this.withFlair = withFlair; RelativeSizeAxes = Axes.Both; Masking = true; @@ -116,7 +120,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score) + Child = new AccuracyCircle(score, withFlair) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 026ce01857..f8bdf0140c 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - ScorePanelList.AddScore(Score); + ScorePanelList.AddScore(Score, true); if (player != null && allowRetry) { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index ee97ee55eb..6e6227da38 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,6 +85,8 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; + private readonly bool isNewLocalScore; + private Container content; private Container topLayerContainer; @@ -97,9 +99,10 @@ namespace osu.Game.Screens.Ranking private Container middleLayerContentContainer; private Drawable middleLayerContent; - public ScorePanel(ScoreInfo score) + public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; + this.isNewLocalScore = isNewLocalScore; } [BackgroundDependencyLoader] @@ -209,7 +212,7 @@ namespace osu.Game.Screens.Ranking 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).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, isNewLocalScore).With(d => d.Alpha = 0)); break; case PanelState.Contracted: diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0d7d339df0..cc163ba762 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -95,9 +95,10 @@ namespace osu.Game.Screens.Ranking /// Adds a to this list. /// /// The to add. - public ScorePanel AddScore(ScoreInfo score) + /// Whether this is a score that has just been achieved locally. Controls whether flair is added to the display or not. + public ScorePanel AddScore(ScoreInfo score, bool isNewLocalScore = false) { - var panel = new ScorePanel(score) + var panel = new ScorePanel(score, isNewLocalScore) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 11f85779d5222f24fa9d0edd8097398417407b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:03:45 +0900 Subject: [PATCH 248/322] Fix panel expanded state being updated multiple times unnecessarily --- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index cc163ba762..e85580a734 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -119,7 +119,10 @@ namespace osu.Game.Screens.Ranking })); if (SelectedScore.Value == score) - selectedScoreChanged(new ValueChangedEvent(SelectedScore.Value, SelectedScore.Value)); + { + if (IsLoaded) + SelectedScore.TriggerChange(); + } else { // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. @@ -143,11 +146,15 @@ namespace osu.Game.Screens.Ranking /// The to present. private void selectedScoreChanged(ValueChangedEvent score) { - // Contract the old panel. - foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + // avoid contracting panels unnecessarily when TriggerChange is fired manually. + if (score.OldValue != score.NewValue) { - t.Panel.State = PanelState.Contracted; - t.Margin = new MarginPadding(); + // Contract the old panel. + foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + { + t.Panel.State = PanelState.Contracted; + t.Margin = new MarginPadding(); + } } // Find the panel corresponding to the new score. From 0a0239a7c799b88049f4a4ca524a58d6f6839a2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:04:33 +0900 Subject: [PATCH 249/322] Only play results panel animation once (and only for the local user) --- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 3 +++ osu.Game/Screens/Ranking/ScorePanel.cs | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5f8609d190..711763330c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -270,6 +270,9 @@ namespace osu.Game.Screens.Ranking.Expanded delay += 200; } } + + if (!withFlair) + FinishTransforms(true); }); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 6e6227da38..df710e4eb8 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - private readonly bool isNewLocalScore; + private bool displayWithFlair; private Container content; @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Ranking public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; - this.isNewLocalScore = isNewLocalScore; + displayWithFlair = isNewLocalScore; } [BackgroundDependencyLoader] @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Ranking state = value; - if (LoadState >= LoadState.Ready) + if (IsLoaded) updateState(); StateChanged?.Invoke(value); @@ -212,7 +212,10 @@ namespace osu.Game.Screens.Ranking 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, isNewLocalScore).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); + + // only the first expanded display should happen with flair. + displayWithFlair = false; break; case PanelState.Contracted: From 4dec46b33e977e8351f916a2c60c71a53a20b08a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:52:58 +0900 Subject: [PATCH 250/322] Attempt to fix in a less destructive way for now --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 8886188d95..e32ed07863 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual if (beatmap.HitObjects.Count > 0) // add buffer after last hitobject to allow for final replay frames etc. - trackLength = beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000; + trackLength = Math.Max(trackLength, beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000); if (referenceClock != null) { From f1b8a8f7f56337edd8693ea2cb8bf57c6c6bd5ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 18:16:04 +0900 Subject: [PATCH 251/322] Remove unused using --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index c6d4b66724..bca3a07fa6 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; From d69d78ab5d9278aaf31d20bd2895f664d1e3c2f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:20:10 +0900 Subject: [PATCH 252/322] Update namespace references --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 + osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index fde42bec04..9bfb6aa839 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 9a0217a1eb..4cadfa9ad4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 3491dea9e2aece3ab76f0b931a5c9ef599e6eba4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 18:51:54 +0900 Subject: [PATCH 253/322] Fix scroll logic running before children may be alive in flow --- osu.Game/Screens/Ranking/ScorePanelList.cs | 45 ++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e85580a734..4325d317c4 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -118,22 +118,24 @@ namespace osu.Game.Screens.Ranking d.Origin = Anchor.Centre; })); - if (SelectedScore.Value == score) + if (IsLoaded) { - if (IsLoaded) - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + if (SelectedScore.Value == score) { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } } } @@ -170,12 +172,15 @@ namespace osu.Game.Screens.Ranking expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - // Scroll to the new panel. This is done manually since we need: - // 1) To scroll after the scroll container's visible range is updated. - // 2) To account for the centre anchor/origins of panels. - // In the end, it's easier to compute the scroll position manually. - float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); - scroll.ScrollTo(scrollOffset); + SchedulerAfterChildren.Add(() => + { + // Scroll to the new panel. This is done manually since we need: + // 1) To scroll after the scroll container's visible range is updated. + // 2) To account for the centre anchor/origins of panels. + // In the end, it's easier to compute the scroll position manually. + float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + scroll.ScrollTo(scrollOffset); + }); } protected override void Update() From 7be4dfabd8d384107b5a21a78ae6fa97f09fbeaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 20:23:15 +0900 Subject: [PATCH 254/322] Revert "Update namespace references" This reverts commit d69d78ab5d9278aaf31d20bd2895f664d1e3c2f1. --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 - osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index 9bfb6aa839..fde42bec04 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 4cadfa9ad4..9a0217a1eb 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 1c353b4745f1ea2bf2cb1a754d47478b7221c2b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 20:38:28 +0900 Subject: [PATCH 255/322] 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 27846fdf53..a4bcbd289d 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 609ac0e5f9..9be933c74a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ebd38bc334..e26f8cc8b4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 0c540537c9f3dde85e4ac74718be9d71a07c19b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 14:30:50 +0100 Subject: [PATCH 256/322] Revert "Add BackgroundSource.Seasonal" This reverts commit 2871001cc294da01f2db8e8c35d84a86ab1503ac. --- osu.Game/Configuration/BackgroundSource.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Configuration/BackgroundSource.cs b/osu.Game/Configuration/BackgroundSource.cs index beef9ef1de..5726e96eb1 100644 --- a/osu.Game/Configuration/BackgroundSource.cs +++ b/osu.Game/Configuration/BackgroundSource.cs @@ -6,7 +6,6 @@ namespace osu.Game.Configuration public enum BackgroundSource { Skin, - Beatmap, - Seasonal + Beatmap } } From 7d523fee2896c71b7bb919cf99f41e88bb3ed2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 14:31:07 +0100 Subject: [PATCH 257/322] Revert "Set BackgroundSource.Seasonal as default setting" This reverts commit cdb2d23578e6de7ca266a23a51b5fac2ed15b8f4. --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 5c5af701bb..7d601c0cb9 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -130,7 +130,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IntroSequence, IntroSequence.Triangles); - Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Seasonal); + Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); } public OsuConfigManager(Storage storage) From b189e0b7cfd8e761b313ab3b1c27371ff270a056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 16:01:22 +0100 Subject: [PATCH 258/322] Revert "Load SeasonalBackgroundLoader asynchronously" This reverts commit 81ebcd879668eb13cb28aa11bf4edfb8afb0fb99. --- .../Backgrounds/BackgroundScreenDefault.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index ec91dcc99f..ef41c5be3d 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -25,7 +25,6 @@ namespace osu.Game.Screens.Backgrounds private Bindable skin; private Bindable mode; private Bindable introSequence; - private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); [Resolved] private IBindable beatmap { get; set; } @@ -51,7 +50,7 @@ namespace osu.Game.Screens.Backgrounds currentDisplay = RNG.Next(0, background_count); - LoadComponentAsync(seasonalBackgroundLoader, _ => LoadComponentAsync(createBackground(), display)); + display(createBackground()); } private void display(Background newBackground) @@ -91,10 +90,6 @@ namespace osu.Game.Screens.Backgrounds { switch (mode.Value) { - case BackgroundSource.Seasonal: - newBackground = seasonalBackgroundLoader.LoadBackground(backgroundName); - break; - case BackgroundSource.Beatmap: newBackground = new BeatmapBackground(beatmap.Value, backgroundName); break; @@ -105,18 +100,7 @@ namespace osu.Game.Screens.Backgrounds } } else - { - switch (mode.Value) - { - case BackgroundSource.Seasonal: - newBackground = seasonalBackgroundLoader.LoadBackground(backgroundName); - break; - - default: - newBackground = new Background(backgroundName); - break; - } - } + newBackground = new Background(backgroundName); newBackground.Depth = currentDisplay; From 76c0a790b404ef1afd65e30078473126a8b677c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 17:28:04 +0100 Subject: [PATCH 259/322] Add separate Seasonal Backgrounds setting (Always, Sometimes, Never) --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Configuration/SeasonalBackgrounds.cs | 12 ++++++++++++ .../Settings/Sections/Audio/MainMenuSettings.cs | 6 ++++++ 3 files changed, 20 insertions(+) create mode 100644 osu.Game/Configuration/SeasonalBackgrounds.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d601c0cb9..9f7280eef4 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -131,6 +131,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IntroSequence, IntroSequence.Triangles); Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); + Set(OsuSetting.SeasonalBackgrounds, SeasonalBackgrounds.Sometimes); } public OsuConfigManager(Storage storage) @@ -239,5 +240,6 @@ namespace osu.Game.Configuration HitLighting, MenuBackgroundSource, GameplayDisableWinKey, + SeasonalBackgrounds } } diff --git a/osu.Game/Configuration/SeasonalBackgrounds.cs b/osu.Game/Configuration/SeasonalBackgrounds.cs new file mode 100644 index 0000000000..7708ae584f --- /dev/null +++ b/osu.Game/Configuration/SeasonalBackgrounds.cs @@ -0,0 +1,12 @@ +// 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.Configuration +{ + public enum SeasonalBackgrounds + { + Always, + Sometimes, + Never + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index d5de32ed05..ee57c0cfa6 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -39,6 +39,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio LabelText = "Background source", Current = config.GetBindable(OsuSetting.MenuBackgroundSource), Items = Enum.GetValues(typeof(BackgroundSource)).Cast() + }, + new SettingsDropdown + { + LabelText = "Seasonal backgrounds", + Current = config.GetBindable(OsuSetting.SeasonalBackgrounds), + Items = Enum.GetValues(typeof(SeasonalBackgrounds)).Cast() } }; } From 907e1921c720fc99cf9d76d135744fdf3d52fbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 17:31:42 +0100 Subject: [PATCH 260/322] Make SeasonalBackgroundLoader read from SessionStatics --- osu.Game/Configuration/SessionStatics.cs | 7 ++++++- .../Backgrounds/SeasonalBackgroundLoader.cs | 19 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 40b2adb867..326abed8fe 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.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.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; + namespace osu.Game.Configuration { /// @@ -12,12 +15,14 @@ namespace osu.Game.Configuration { Set(Static.LoginOverlayDisplayed, false); Set(Static.MutedAudioNotificationShownOnce, false); + Set(Static.SeasonalBackgrounds, new List()); } } public enum Static { LoginOverlayDisplayed, - MutedAudioNotificationShownOnce + MutedAudioNotificationShownOnce, + SeasonalBackgrounds } } diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index af81b25cee..72785be3b4 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -5,9 +5,11 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -17,17 +19,20 @@ namespace osu.Game.Graphics.Backgrounds [LongRunningLoad] public class SeasonalBackgroundLoader : Component { - private List backgrounds = new List(); + private Bindable> backgrounds; private int current; [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load(SessionStatics sessionStatics, IAPIProvider api) { + backgrounds = sessionStatics.GetBindable>(Static.SeasonalBackgrounds); + if (backgrounds.Value.Any()) return; + var request = new GetSeasonalBackgroundsRequest(); request.Success += response => { - backgrounds = response.Backgrounds ?? backgrounds; - current = RNG.Next(0, backgrounds.Count); + backgrounds.Value = response.Backgrounds ?? backgrounds.Value; + current = RNG.Next(0, backgrounds.Value.Count); }; api.PerformAsync(request); @@ -37,10 +42,10 @@ namespace osu.Game.Graphics.Backgrounds { string url = null; - if (backgrounds.Any()) + if (backgrounds.Value.Any()) { - current = (current + 1) % backgrounds.Count; - url = backgrounds[current].Url; + current = (current + 1) % backgrounds.Value.Count; + url = backgrounds.Value[current].Url; } return new SeasonalBackground(url, fallbackTextureName); From bf4d99dfe7aa3ad5a4422430ae2079e4ef93d7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 17:43:10 +0100 Subject: [PATCH 261/322] Load SeasonalBackgroundLoader asynchronously --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index ef41c5be3d..98552dda71 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Backgrounds private Bindable skin; private Bindable mode; private Bindable introSequence; + private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); [Resolved] private IBindable beatmap { get; set; } @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Backgrounds currentDisplay = RNG.Next(0, background_count); - display(createBackground()); + LoadComponentAsync(seasonalBackgroundLoader, _ => LoadComponentAsync(createBackground(), display)); } private void display(Background newBackground) From 34371b8888980ec2de400b16924657449263ea1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 17:44:23 +0100 Subject: [PATCH 262/322] Show next Background on showSeasonalBackgrounds.ValueChanged --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 98552dda71..1be19c5854 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Backgrounds private Bindable skin; private Bindable mode; private Bindable introSequence; + private Bindable showSeasonalBackgrounds; private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); [Resolved] @@ -42,12 +43,14 @@ namespace osu.Game.Screens.Backgrounds skin = skinManager.CurrentSkin.GetBoundCopy(); mode = config.GetBindable(OsuSetting.MenuBackgroundSource); introSequence = config.GetBindable(OsuSetting.IntroSequence); + showSeasonalBackgrounds = config.GetBindable(OsuSetting.SeasonalBackgrounds); user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); mode.ValueChanged += _ => Next(); beatmap.ValueChanged += _ => Next(); introSequence.ValueChanged += _ => Next(); + showSeasonalBackgrounds.ValueChanged += _ => Next(); currentDisplay = RNG.Next(0, background_count); From d9846fad37d4d2b9453621c2b225b9a3cf12cc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 18:03:36 +0100 Subject: [PATCH 263/322] Remove fallback texture parameter When there isn't a seasonal event, we don't want to fall back to the basic background here, but rather to the user selected background source. --- .../Backgrounds/SeasonalBackgroundLoader.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index 72785be3b4..abd9664106 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -38,17 +37,14 @@ namespace osu.Game.Graphics.Backgrounds api.PerformAsync(request); } - public SeasonalBackground LoadBackground(string fallbackTextureName) + public SeasonalBackground LoadBackground() { - string url = null; + if (!backgrounds.Value.Any()) return null; - if (backgrounds.Value.Any()) - { - current = (current + 1) % backgrounds.Value.Count; - url = backgrounds.Value[current].Url; - } + current = (current + 1) % backgrounds.Value.Count; + string url = backgrounds.Value[current].Url; - return new SeasonalBackground(url, fallbackTextureName); + return new SeasonalBackground(url); } } @@ -56,18 +52,17 @@ namespace osu.Game.Graphics.Backgrounds public class SeasonalBackground : Background { private readonly string url; - private readonly string fallbackTextureName; + private const string fallback_texture_name = @"Backgrounds/bg1"; - public SeasonalBackground([CanBeNull] string url, string fallbackTextureName = @"Backgrounds/bg1") + public SeasonalBackground(string url) { this.url = url; - this.fallbackTextureName = fallbackTextureName; } [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - Sprite.Texture = textures.Get(url) ?? textures.Get(fallbackTextureName); + Sprite.Texture = textures.Get(url) ?? textures.Get(fallback_texture_name); } } } From fb1e09b3e793fae71caada4ad754d586ce7f5a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 29 Oct 2020 18:04:48 +0100 Subject: [PATCH 264/322] Load seasonal backgrounds according to setting --- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 1be19c5854..70eafd4aff 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -106,6 +106,18 @@ namespace osu.Game.Screens.Backgrounds else newBackground = new Background(backgroundName); + switch (showSeasonalBackgrounds.Value) + { + case SeasonalBackgrounds.Sometimes: + if (RNG.NextBool()) + goto case SeasonalBackgrounds.Always; + break; + + case SeasonalBackgrounds.Always: + newBackground = seasonalBackgroundLoader.LoadBackground() ?? newBackground; + break; + } + newBackground.Depth = currentDisplay; return newBackground; From 0c1d12460fcc0304ce1889e21c684da5f107f59d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 10:30:11 +0900 Subject: [PATCH 265/322] Remove unused parameter --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index f69ccc1773..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, false) + 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 337665b51f..45da23f1f9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -78,7 +78,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 711763330c..cb4560802b 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -120,7 +120,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, From c9a85587fb21be9c1d54e448e26f0b930e664ea0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:32:03 +0900 Subject: [PATCH 266/322] Allow SampleInfo to specify fallback sample lookup names --- osu.Game/Audio/SampleInfo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 2406b0bef2..240d70c418 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -10,14 +10,14 @@ namespace osu.Game.Audio /// public class SampleInfo : ISampleInfo { - private readonly string sampleName; + private readonly string[] sampleNames; - public SampleInfo(string sampleName) + public SampleInfo(params string[] sampleNames) { - this.sampleName = sampleName; + this.sampleNames = sampleNames; } - public IEnumerable LookupNames => new[] { sampleName }; + public IEnumerable LookupNames => sampleNames; public int Volume { get; } = 100; } From 0b28cca7e6b53b7e3be67782f00ea9a12b55cfa9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:32:20 +0900 Subject: [PATCH 267/322] Don't force Gameplay prefix on all skin sample lookups --- osu.Game/Audio/HitSampleInfo.cs | 4 ++-- osu.Game/Skinning/SkinnableSound.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 8b1f5a366a..8efaeb3795 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -50,9 +50,9 @@ namespace osu.Game.Audio get { if (!string.IsNullOrEmpty(Suffix)) - yield return $"{Bank}-{Name}{Suffix}"; + yield return $"Gameplay/{Bank}-{Name}{Suffix}"; - yield return $"{Bank}-{Name}"; + yield return $"Gameplay/{Bank}-{Name}"; } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f6e91811dd..ffa0a963ce 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning { foreach (var lookup in s.LookupNames) { - if ((ch = samples.Get($"Gameplay/{lookup}")) != null) + if ((ch = samples.Get(lookup)) != null) break; } } From d319b27b3d9b90c8d69e48dcdd137d8ec08be566 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 11:14:08 +0900 Subject: [PATCH 268/322] Run sample lookup logic through getFallbackNames --- osu.Game/Skinning/LegacySkin.cs | 54 +++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 94b09684d3..d927d54abc 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -417,10 +417,14 @@ namespace osu.Game.Skinning public override SampleChannel GetSample(ISampleInfo sampleInfo) { - var lookupNames = sampleInfo.LookupNames; + IEnumerable lookupNames = null; if (sampleInfo is HitSampleInfo hitSample) lookupNames = getLegacyLookupNames(hitSample); + else + { + lookupNames = sampleInfo.LookupNames.SelectMany(getFallbackNames); + } foreach (var lookup in lookupNames) { @@ -433,6 +437,36 @@ namespace osu.Game.Skinning return null; } + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) + { + var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); + + if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) + { + // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. + // using .EndsWith() is intentional as it ensures parity in all edge cases + // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). + foreach (var l in lookupNames) + { + if (!l.EndsWith(hitSample.Suffix, StringComparison.Ordinal)) + { + foreach (var n in getFallbackNames(l)) + yield return n; + } + } + } + else + { + foreach (var l in lookupNames) + yield return l; + } + + // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. + // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, + // which is why this is done locally here. + yield return hitSample.Name; + } + private IEnumerable getFallbackNames(string componentName) { // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. @@ -442,23 +476,5 @@ namespace osu.Game.Skinning string lastPiece = componentName.Split('/').Last(); yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; } - - private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) - { - var lookupNames = hitSample.LookupNames; - - if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) - // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. - // using .EndsWith() is intentional as it ensures parity in all edge cases - // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). - lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); - - // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. - // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, - // which is why this is done locally here. - lookupNames = lookupNames.Append(hitSample.Name); - - return lookupNames; - } } } From 2ea4aa0a37c86b74f67cfb3f493e882e0adbd335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 11:52:25 +0900 Subject: [PATCH 269/322] Fix incorrect specification on some sample lookups --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 8 ++++---- osu.Game/Screens/Play/ComboEffects.cs | 2 +- osu.Game/Screens/Play/PauseOverlay.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 864e88d023..fc0cda2c1f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay skinSource = new TestSkinSourceContainer { RelativeSizeAxes = Axes.Both, - Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) + Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("Gameplay/normal-sliderslide")) }, }; }); diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 282de3a8e1..e8b051b4d9 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mods { InternalChildren = new Drawable[] { - hatSample = new PausableSkinnableSound(new SampleInfo("nightcore-hat")), - clapSample = new PausableSkinnableSound(new SampleInfo("nightcore-clap")), - kickSample = new PausableSkinnableSound(new SampleInfo("nightcore-kick")), - finishSample = new PausableSkinnableSound(new SampleInfo("nightcore-finish")), + hatSample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-hat")), + clapSample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-clap")), + kickSample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-kick")), + finishSample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-finish")), }; } diff --git a/osu.Game/Screens/Play/ComboEffects.cs b/osu.Game/Screens/Play/ComboEffects.cs index 5bcda50399..831b2f593c 100644 --- a/osu.Game/Screens/Play/ComboEffects.cs +++ b/osu.Game/Screens/Play/ComboEffects.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - InternalChild = comboBreakSample = new SkinnableSound(new SampleInfo("combobreak")); + InternalChild = comboBreakSample = new SkinnableSound(new SampleInfo("Gameplay/combobreak")); alwaysPlay = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak); } diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 65f34aba3e..8778cff535 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) + AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("Gameplay/pause-loop")) { Looping = true, Volume = { Value = 0 } From 2ec2749cb49aec0683217116904fb1f526ea26a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 11:52:08 +0900 Subject: [PATCH 270/322] Fix taiko lookup logic --- osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index a804ea5f82..c88480d18f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning get { foreach (var name in source.LookupNames) - yield return $"taiko-{name}"; + yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-"); foreach (var name in source.LookupNames) yield return name; From fed4accfeab0100cdbcc3af7e292a1e408cf62c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 12:12:30 +0900 Subject: [PATCH 271/322] Update tests to refect new mappings --- .../convert-samples-expected-conversion.json | 16 ++++++++-------- .../mania-samples-expected-conversion.json | 8 ++++---- ...er-convert-samples-expected-conversion.json | 6 +++--- .../Formats/LegacyBeatmapDecoderTest.cs | 18 +++++++++--------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json index d49ffa01c5..6f1d45ad8c 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -6,20 +6,20 @@ "EndTime": 2750.0, "Column": 1, "NodeSamples": [ - ["normal-hitnormal"], - ["soft-hitnormal"], - ["drum-hitnormal"] + ["Gameplay/normal-hitnormal"], + ["Gameplay/soft-hitnormal"], + ["Gameplay/drum-hitnormal"] ], - "Samples": ["-hitnormal"] + "Samples": ["Gameplay/-hitnormal"] }, { "StartTime": 1875.0, "EndTime": 2750.0, "Column": 0, "NodeSamples": [ - ["soft-hitnormal"], - ["drum-hitnormal"] + ["Gameplay/soft-hitnormal"], + ["Gameplay/drum-hitnormal"] ], - "Samples": ["-hitnormal"] + "Samples": ["Gameplay/-hitnormal"] }] }, { "StartTime": 3750.0, @@ -27,7 +27,7 @@ "StartTime": 3750.0, "EndTime": 3750.0, "Column": 3, - "Samples": ["normal-hitnormal"] + "Samples": ["Gameplay/normal-hitnormal"] }] }] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json index 1aca75a796..fd0c0cad60 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json @@ -6,10 +6,10 @@ "EndTime": 1500.0, "Column": 0, "NodeSamples": [ - ["normal-hitnormal"], + ["Gameplay/normal-hitnormal"], [] ], - "Samples": ["normal-hitnormal"] + "Samples": ["Gameplay/normal-hitnormal"] }] }, { "StartTime": 2000.0, @@ -18,10 +18,10 @@ "EndTime": 3000.0, "Column": 2, "NodeSamples": [ - ["drum-hitnormal"], + ["Gameplay/drum-hitnormal"], [] ], - "Samples": ["drum-hitnormal"] + "Samples": ["Gameplay/drum-hitnormal"] }] }] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json index e3768a90d7..e07bd3c47c 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json @@ -5,17 +5,17 @@ "StartTime": 8470.0, "EndTime": 8470.0, "Column": 0, - "Samples": ["normal-hitnormal", "normal-hitclap"] + "Samples": ["Gameplay/normal-hitnormal", "Gameplay/normal-hitclap"] }, { "StartTime": 8626.470587768974, "EndTime": 8626.470587768974, "Column": 1, - "Samples": ["normal-hitnormal"] + "Samples": ["Gameplay/normal-hitnormal"] }, { "StartTime": 8782.941175537948, "EndTime": 8782.941175537948, "Column": 2, - "Samples": ["normal-hitnormal", "normal-hitclap"] + "Samples": ["Gameplay/normal-hitnormal", "Gameplay/normal-hitclap"] }] }] } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index b6e1af57fd..4b9e9dd88c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -410,13 +410,13 @@ namespace osu.Game.Tests.Beatmaps.Formats { var hitObjects = decoder.Decode(stream).HitObjects; - Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First()); - Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); - Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); - Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); // The control point at the end time of the slider should be applied - Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); + Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); } static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); @@ -432,9 +432,9 @@ namespace osu.Game.Tests.Beatmaps.Formats { var hitObjects = decoder.Decode(stream).HitObjects; - Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First()); - Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); - Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); } static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); @@ -452,7 +452,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[0]).LookupNames.First()); Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); - Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); + Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); } From b906736b85efdd9bdc739aab9fdeae74a5f43ad5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 12:28:40 +0900 Subject: [PATCH 272/322] Remove redundant initialisation --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d927d54abc..4dea42cf92 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -417,7 +417,7 @@ namespace osu.Game.Skinning public override SampleChannel GetSample(ISampleInfo sampleInfo) { - IEnumerable lookupNames = null; + IEnumerable lookupNames; if (sampleInfo is HitSampleInfo hitSample) lookupNames = getLegacyLookupNames(hitSample); From 46d89d55f4e9b33edc97cb3696cf2a9ebcee7727 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 12:46:48 +0900 Subject: [PATCH 273/322] Add note about ScheduleAfterChildren requirement --- osu.Game/Screens/Ranking/ScorePanelList.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4325d317c4..77b3d8fc3b 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -172,7 +172,8 @@ namespace osu.Game.Screens.Ranking expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - SchedulerAfterChildren.Add(() => + // requires schedule after children to ensure the flow (and thus ScrollContainer's ScrollableExtent) has been updated. + ScheduleAfterChildren(() => { // Scroll to the new panel. This is done manually since we need: // 1) To scroll after the scroll container's visible range is updated. From 18f92818daed770f18d35fc74cc22c4da392e567 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:09:13 +0900 Subject: [PATCH 274/322] Show current HUD visibility mode as a tracked setting --- osu.Game/Configuration/OsuConfigManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d601c0cb9..46c5e61784 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -170,6 +170,7 @@ namespace osu.Game.Configuration public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")), + new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription())), new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())), }; } From 9bb86ccb832d8f838517c5d4ef7af8d018d2ed38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:09:22 +0900 Subject: [PATCH 275/322] Change shift-tab to cycle available HUD visibility modes --- osu.Game/Screens/Play/HUDOverlay.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b047d44f8a..623041d9ca 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -277,9 +277,25 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - configVisibilityMode.Value = configVisibilityMode.Value != HUDVisibilityMode.Never - ? HUDVisibilityMode.Never - : HUDVisibilityMode.HideDuringGameplay; + switch (configVisibilityMode.Value) + { + case HUDVisibilityMode.Never: + configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay; + break; + + case HUDVisibilityMode.HideDuringGameplay: + configVisibilityMode.Value = HUDVisibilityMode.HideDuringBreaks; + break; + + case HUDVisibilityMode.HideDuringBreaks: + configVisibilityMode.Value = HUDVisibilityMode.Always; + break; + + case HUDVisibilityMode.Always: + configVisibilityMode.Value = HUDVisibilityMode.Never; + break; + } + return true; } } From f58f8e0f93a66d1553e63f151e9e63ddafe0475b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:46:54 +0900 Subject: [PATCH 276/322] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2d531cf01e..b3100d268b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ca588b89d9..54f86e5839 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9c22dec330..6100b55334 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From c72017a7db4ad00ba2b63d976fca20c5ea9ac583 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:49:44 +0900 Subject: [PATCH 277/322] Remove "hide during breaks" option Probably wouldn't be used anyway. --- osu.Game/Configuration/HUDVisibilityMode.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs index b0b55dd811..10f3f65355 100644 --- a/osu.Game/Configuration/HUDVisibilityMode.cs +++ b/osu.Game/Configuration/HUDVisibilityMode.cs @@ -12,9 +12,6 @@ namespace osu.Game.Configuration [Description("Hide during gameplay")] HideDuringGameplay, - [Description("Hide during breaks")] - HideDuringBreaks, - Always } } From b4eda65383cf80c56ac4991887836a12bb5a5be8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:53:51 +0900 Subject: [PATCH 278/322] Commit missing pieces --- osu.Game/Screens/Play/HUDOverlay.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 623041d9ca..0cfe6effc1 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -223,11 +223,6 @@ namespace osu.Game.Screens.Play ShowHud.Value = false; break; - case HUDVisibilityMode.HideDuringBreaks: - // always show during replay as we want the seek bar to be visible. - ShowHud.Value = replayLoaded.Value || !IsBreakTime.Value; - break; - case HUDVisibilityMode.HideDuringGameplay: // always show during replay as we want the seek bar to be visible. ShowHud.Value = replayLoaded.Value || IsBreakTime.Value; @@ -284,10 +279,6 @@ namespace osu.Game.Screens.Play break; case HUDVisibilityMode.HideDuringGameplay: - configVisibilityMode.Value = HUDVisibilityMode.HideDuringBreaks; - break; - - case HUDVisibilityMode.HideDuringBreaks: configVisibilityMode.Value = HUDVisibilityMode.Always; break; From 53bd31c69e6acada773346e350ffc12430ae651d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:00:07 +0900 Subject: [PATCH 279/322] Commit missing test pieces --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 6ec673704c..6764501569 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExternalHideDoesntAffectConfig() { - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; createNew(); From 8928aa6d92990ce761c205e80ac3c20b1a4feffe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:19:40 +0900 Subject: [PATCH 280/322] Add key binding to show HUD while held --- .../Input/Bindings/GlobalActionContainer.cs | 4 +++ osu.Game/Screens/Play/HUDOverlay.cs | 36 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 41be4cfcc3..3de4bb1f9d 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -67,6 +67,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), + new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), }; public IEnumerable AudioControlKeyBindings => new[] @@ -187,5 +188,8 @@ namespace osu.Game.Input.Bindings [Description("Timing Mode")] EditorTimingMode, + + [Description("Hold for HUD")] + HoldForHUD, } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b047d44f8a..c38c2ee5f7 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -8,8 +8,10 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -22,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Screens.Play { [Cached] - public class HUDOverlay : Container + public class HUDOverlay : Container, IKeyBindingHandler { public const float FADE_DURATION = 400; @@ -67,6 +69,8 @@ namespace osu.Game.Screens.Play internal readonly IBindable IsBreakTime = new Bindable(); + private bool holdingForHUD; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -217,6 +221,12 @@ namespace osu.Game.Screens.Play if (ShowHud.Disabled) return; + if (holdingForHUD) + { + ShowHud.Value = true; + return; + } + switch (configVisibilityMode.Value) { case HUDVisibilityMode.Never: @@ -351,5 +361,29 @@ namespace osu.Game.Screens.Play HealthDisplay?.BindHealthProcessor(processor); FailingLayer?.BindHealthProcessor(processor); } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.HoldForHUD: + holdingForHUD = true; + updateVisibility(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.HoldForHUD: + holdingForHUD = false; + updateVisibility(); + break; + } + } } } From bd7871d9f511b09ddd224759e3b9365f043c27d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:20:00 +0900 Subject: [PATCH 281/322] Update test scene to be non-skinnable (and add test covering momentary display) --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 6ec673704c..136c9e191d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -2,29 +2,23 @@ // 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.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneHUDOverlay : SkinnableTestScene + public class TestSceneHUDOverlay : OsuManualInputManagerTestScene { private HUDOverlay hudOverlay; - private IEnumerable hudOverlays => CreatedDrawables.OfType(); - // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); @@ -37,17 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddRepeatStep("increase combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value++; - }, 10); + AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10); - AddStep("reset combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value = 0; - }); + AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; }); } [Test] @@ -77,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); @@ -86,6 +72,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); } + [Test] + public void TestMomentaryShowHUD() + { + createNew(); + + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); + + AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); + + AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + + AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft)); + AddUntilStep("wait for visible", () => hideTarget.IsPresent); + + AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + + AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue)); + } + [Test] public void TestExternalHideDoesntAffectConfig() { @@ -113,14 +120,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set keycounter visible false", () => { config.Set(OsuSetting.KeyOverlay, false); - hudOverlays.ForEach(h => h.KeyCounter.AlwaysVisible.Value = false); + hudOverlay.KeyCounter.AlwaysVisible.Value = false; }); - AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); - AddStep("set showhud true", () => hudOverlays.ForEach(h => h.ShowHud.Value = true)); + AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); @@ -131,22 +138,17 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - SetContents(() => - { - hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); - hudOverlay.ComboCounter.Current.Value = 1; + hudOverlay.ComboCounter.Current.Value = 1; - action?.Invoke(hudOverlay); + action?.Invoke(hudOverlay); - return hudOverlay; - }); + Child = hudOverlay; }); } - - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } From 984a243eff796505ee9f2ca6e85ae2a00233e3a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:23:24 +0900 Subject: [PATCH 282/322] Add skinnable test scene for HUD overlay --- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs new file mode 100644 index 0000000000..fec1610160 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -0,0 +1,99 @@ +// 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.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableHUDOverlay : SkinnableTestScene + { + private HUDOverlay hudOverlay; + + private IEnumerable hudOverlays => CreatedDrawables.OfType(); + + // best way to check without exposing. + private Drawable hideTarget => hudOverlay.KeyCounter; + private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); + + [Resolved] + private OsuConfigManager config { get; set; } + + [Test] + public void TestComboCounterIncrementing() + { + createNew(); + + AddRepeatStep("increase combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value++; + }, 10); + + AddStep("reset combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value = 0; + }); + } + + [Test] + public void TestFadesInOnLoadComplete() + { + float? initialAlpha = null; + + createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); + AddUntilStep("wait for load", () => hudOverlay.IsAlive); + AddAssert("initial alpha was less than 1", () => initialAlpha < 1); + } + + [Test] + public void TestHideExternally() + { + createNew(); + + AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + + AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); + + // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. + AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); + } + + private void createNew(Action action = null) + { + AddStep("create overlay", () => + { + SetContents(() => + { + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + + hudOverlay.ComboCounter.Current.Value = 1; + + action?.Invoke(hudOverlay); + + return hudOverlay; + }); + }); + } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + } +} From 43f9c1ebead2fb96e7f5994868ce43c93133d3ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Oct 2020 18:26:38 +0900 Subject: [PATCH 283/322] Fix HUD test having out of date value --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 9744575878..f9914e0193 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -77,7 +77,8 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); From f27ce7521d69295e7af78478118e436db6f400ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 10:27:43 +0100 Subject: [PATCH 284/322] Make "Sometimes" setting depend on season end date, rather than chance --- osu.Game/Configuration/SessionStatics.cs | 5 ++++- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 8 ++++++++ .../API/Requests/Responses/APISeasonalBackgrounds.cs | 4 ++++ osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 326abed8fe..8100e0fb12 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.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.Collections.Generic; using osu.Game.Online.API.Requests.Responses; @@ -15,6 +16,7 @@ namespace osu.Game.Configuration { Set(Static.LoginOverlayDisplayed, false); Set(Static.MutedAudioNotificationShownOnce, false); + Set(Static.SeasonEndDate, DateTimeOffset.MinValue); Set(Static.SeasonalBackgrounds, new List()); } } @@ -23,6 +25,7 @@ namespace osu.Game.Configuration { LoginOverlayDisplayed, MutedAudioNotificationShownOnce, - SeasonalBackgrounds + SeasonEndDate, + SeasonalBackgrounds, } } diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index abd9664106..d806c62650 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -18,19 +19,24 @@ namespace osu.Game.Graphics.Backgrounds [LongRunningLoad] public class SeasonalBackgroundLoader : Component { + private Bindable endDate; private Bindable> backgrounds; private int current; [BackgroundDependencyLoader] private void load(SessionStatics sessionStatics, IAPIProvider api) { + endDate = sessionStatics.GetBindable(Static.SeasonEndDate); backgrounds = sessionStatics.GetBindable>(Static.SeasonalBackgrounds); + if (backgrounds.Value.Any()) return; var request = new GetSeasonalBackgroundsRequest(); request.Success += response => { + endDate.Value = response.EndDate; backgrounds.Value = response.Backgrounds ?? backgrounds.Value; + current = RNG.Next(0, backgrounds.Value.Count); }; @@ -46,6 +52,8 @@ namespace osu.Game.Graphics.Backgrounds return new SeasonalBackground(url); } + + public bool IsInSeason() => DateTimeOffset.Now < endDate.Value; } [LongRunningLoad] diff --git a/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs b/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs index 6996ac4d9b..8e395f7397 100644 --- a/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs +++ b/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.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.Collections.Generic; using Newtonsoft.Json; @@ -8,6 +9,9 @@ namespace osu.Game.Online.API.Requests.Responses { public class APISeasonalBackgrounds { + [JsonProperty("ends_at")] + public DateTimeOffset EndDate; + [JsonProperty("backgrounds")] public List Backgrounds { get; set; } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 70eafd4aff..de73c82d5c 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Backgrounds switch (showSeasonalBackgrounds.Value) { case SeasonalBackgrounds.Sometimes: - if (RNG.NextBool()) + if (seasonalBackgroundLoader.IsInSeason()) goto case SeasonalBackgrounds.Always; break; From a1fa6588f6a933c97551f78b98e21734f06e0c10 Mon Sep 17 00:00:00 2001 From: cadon0 Date: Sat, 31 Oct 2020 01:03:57 +1300 Subject: [PATCH 285/322] Fix "bounce" when metadata container text is empty --- osu.Game/Screens/Select/BeatmapDetails.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 0ee52f3e48..71f78c5c95 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -300,6 +300,7 @@ namespace osu.Game.Screens.Select public MetadataSection(string title) { + Alpha = 0; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From bc69ed3870457e25e83b32879e3fc36982f4031d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 22:33:05 +0900 Subject: [PATCH 286/322] Simplify sample lookup --- osu.Game/Skinning/LegacySkin.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 4dea42cf92..fb020f4e39 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -446,21 +446,12 @@ namespace osu.Game.Skinning // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. // using .EndsWith() is intentional as it ensures parity in all edge cases // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). - foreach (var l in lookupNames) - { - if (!l.EndsWith(hitSample.Suffix, StringComparison.Ordinal)) - { - foreach (var n in getFallbackNames(l)) - yield return n; - } - } - } - else - { - foreach (var l in lookupNames) - yield return l; + lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); } + foreach (var l in lookupNames) + yield return l; + // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, // which is why this is done locally here. From 4e3fb615d25c6a2edfbec009401ec73f925840f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 15:54:10 +0100 Subject: [PATCH 287/322] Rename "SeasonalBackgrounds" to "SeasonalBackgroundMode" --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- ...nalBackgrounds.cs => SeasonalBackgroundMode.cs} | 2 +- .../Settings/Sections/Audio/MainMenuSettings.cs | 6 +++--- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Configuration/{SeasonalBackgrounds.cs => SeasonalBackgroundMode.cs} (86%) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e579898c05..e0971d238a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -131,7 +131,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IntroSequence, IntroSequence.Triangles); Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); - Set(OsuSetting.SeasonalBackgrounds, SeasonalBackgrounds.Sometimes); + Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes); } public OsuConfigManager(Storage storage) @@ -241,6 +241,6 @@ namespace osu.Game.Configuration HitLighting, MenuBackgroundSource, GameplayDisableWinKey, - SeasonalBackgrounds + SeasonalBackgroundMode } } diff --git a/osu.Game/Configuration/SeasonalBackgrounds.cs b/osu.Game/Configuration/SeasonalBackgroundMode.cs similarity index 86% rename from osu.Game/Configuration/SeasonalBackgrounds.cs rename to osu.Game/Configuration/SeasonalBackgroundMode.cs index 7708ae584f..406736b2a4 100644 --- a/osu.Game/Configuration/SeasonalBackgrounds.cs +++ b/osu.Game/Configuration/SeasonalBackgroundMode.cs @@ -3,7 +3,7 @@ namespace osu.Game.Configuration { - public enum SeasonalBackgrounds + public enum SeasonalBackgroundMode { Always, Sometimes, diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index ee57c0cfa6..7682967d10 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -40,11 +40,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Current = config.GetBindable(OsuSetting.MenuBackgroundSource), Items = Enum.GetValues(typeof(BackgroundSource)).Cast() }, - new SettingsDropdown + new SettingsDropdown { LabelText = "Seasonal backgrounds", - Current = config.GetBindable(OsuSetting.SeasonalBackgrounds), - Items = Enum.GetValues(typeof(SeasonalBackgrounds)).Cast() + Current = config.GetBindable(OsuSetting.SeasonalBackgroundMode), + Items = Enum.GetValues(typeof(SeasonalBackgroundMode)).Cast() } }; } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index de73c82d5c..a5bdcee8d4 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Backgrounds private Bindable skin; private Bindable mode; private Bindable introSequence; - private Bindable showSeasonalBackgrounds; + private Bindable seasonalBackgroundMode; private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); [Resolved] @@ -43,14 +43,14 @@ namespace osu.Game.Screens.Backgrounds skin = skinManager.CurrentSkin.GetBoundCopy(); mode = config.GetBindable(OsuSetting.MenuBackgroundSource); introSequence = config.GetBindable(OsuSetting.IntroSequence); - showSeasonalBackgrounds = config.GetBindable(OsuSetting.SeasonalBackgrounds); + seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); mode.ValueChanged += _ => Next(); beatmap.ValueChanged += _ => Next(); introSequence.ValueChanged += _ => Next(); - showSeasonalBackgrounds.ValueChanged += _ => Next(); + seasonalBackgroundMode.ValueChanged += _ => Next(); currentDisplay = RNG.Next(0, background_count); @@ -106,14 +106,14 @@ namespace osu.Game.Screens.Backgrounds else newBackground = new Background(backgroundName); - switch (showSeasonalBackgrounds.Value) + switch (seasonalBackgroundMode.Value) { - case SeasonalBackgrounds.Sometimes: + case SeasonalBackgroundMode.Sometimes: if (seasonalBackgroundLoader.IsInSeason()) - goto case SeasonalBackgrounds.Always; + goto case SeasonalBackgroundMode.Always; break; - case SeasonalBackgrounds.Always: + case SeasonalBackgroundMode.Always: newBackground = seasonalBackgroundLoader.LoadBackground() ?? newBackground; break; } From d19dd4eef6ba621616f684b5b07d41d154d68f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 15:56:19 +0100 Subject: [PATCH 288/322] IsInSeason() -> IsInSeason --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 2 +- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index d806c62650..ff1a2c9f37 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.Backgrounds return new SeasonalBackground(url); } - public bool IsInSeason() => DateTimeOffset.Now < endDate.Value; + public bool IsInSeason => DateTimeOffset.Now < endDate.Value; } [LongRunningLoad] diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index a5bdcee8d4..5fa4ddc041 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Backgrounds switch (seasonalBackgroundMode.Value) { case SeasonalBackgroundMode.Sometimes: - if (seasonalBackgroundLoader.IsInSeason()) + if (seasonalBackgroundLoader.IsInSeason) goto case SeasonalBackgroundMode.Always; break; From 6f6a8e2a8fe09581630728d0e347b48e91c3f80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 16:06:48 +0100 Subject: [PATCH 289/322] Convert switch to if --- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 5fa4ddc041..39ecd70084 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -106,16 +106,10 @@ namespace osu.Game.Screens.Backgrounds else newBackground = new Background(backgroundName); - switch (seasonalBackgroundMode.Value) + if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Always + || seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && seasonalBackgroundLoader.IsInSeason) { - case SeasonalBackgroundMode.Sometimes: - if (seasonalBackgroundLoader.IsInSeason) - goto case SeasonalBackgroundMode.Always; - break; - - case SeasonalBackgroundMode.Always: - newBackground = seasonalBackgroundLoader.LoadBackground() ?? newBackground; - break; + newBackground = seasonalBackgroundLoader.LoadBackground() ?? newBackground; } newBackground.Depth = currentDisplay; From f6eb5680ec53626707452cf08c0926713fbf2ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 16:43:18 +0100 Subject: [PATCH 290/322] Save full api response in SessionStatics --- osu.Game/Configuration/SessionStatics.cs | 8 ++----- .../Backgrounds/SeasonalBackgroundLoader.cs | 24 ++++++++----------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 8100e0fb12..199889391b 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -1,8 +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 osu.Game.Online.API.Requests.Responses; namespace osu.Game.Configuration @@ -16,8 +14,7 @@ namespace osu.Game.Configuration { Set(Static.LoginOverlayDisplayed, false); Set(Static.MutedAudioNotificationShownOnce, false); - Set(Static.SeasonEndDate, DateTimeOffset.MinValue); - Set(Static.SeasonalBackgrounds, new List()); + Set(Static.SeasonalBackgroundsResponse, null); } } @@ -25,7 +22,6 @@ namespace osu.Game.Configuration { LoginOverlayDisplayed, MutedAudioNotificationShownOnce, - SeasonEndDate, - SeasonalBackgrounds, + SeasonalBackgroundsResponse, } } diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index ff1a2c9f37..c884756c80 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -2,7 +2,6 @@ // 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.Bindables; @@ -19,25 +18,21 @@ namespace osu.Game.Graphics.Backgrounds [LongRunningLoad] public class SeasonalBackgroundLoader : Component { - private Bindable endDate; - private Bindable> backgrounds; + private Bindable cachedResponse; private int current; [BackgroundDependencyLoader] private void load(SessionStatics sessionStatics, IAPIProvider api) { - endDate = sessionStatics.GetBindable(Static.SeasonEndDate); - backgrounds = sessionStatics.GetBindable>(Static.SeasonalBackgrounds); + cachedResponse = sessionStatics.GetBindable(Static.SeasonalBackgroundsResponse); - if (backgrounds.Value.Any()) return; + if (cachedResponse.Value != null) return; var request = new GetSeasonalBackgroundsRequest(); request.Success += response => { - endDate.Value = response.EndDate; - backgrounds.Value = response.Backgrounds ?? backgrounds.Value; - - current = RNG.Next(0, backgrounds.Value.Count); + cachedResponse.Value = response; + current = RNG.Next(0, cachedResponse.Value.Backgrounds.Count); }; api.PerformAsync(request); @@ -45,15 +40,16 @@ namespace osu.Game.Graphics.Backgrounds public SeasonalBackground LoadBackground() { - if (!backgrounds.Value.Any()) return null; + var backgrounds = cachedResponse.Value.Backgrounds; + if (!backgrounds.Any()) return null; - current = (current + 1) % backgrounds.Value.Count; - string url = backgrounds.Value[current].Url; + current = (current + 1) % backgrounds.Count; + string url = backgrounds[current].Url; return new SeasonalBackground(url); } - public bool IsInSeason => DateTimeOffset.Now < endDate.Value; + public bool IsInSeason => DateTimeOffset.Now < cachedResponse.Value.EndDate; } [LongRunningLoad] From 0b46c19b23995f42e35b82877b41404200f109ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 17:16:51 +0100 Subject: [PATCH 291/322] Move seasonalBackgroundMode check up and early return if available --- .../Backgrounds/BackgroundScreenDefault.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 39ecd70084..b65b45060f 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -79,6 +79,18 @@ namespace osu.Game.Screens.Backgrounds Background newBackground; string backgroundName; + if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Always + || seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && seasonalBackgroundLoader.IsInSeason) + { + var seasonalBackground = seasonalBackgroundLoader.LoadBackground(); + + if (seasonalBackground != null) + { + seasonalBackground.Depth = currentDisplay; + return seasonalBackground; + } + } + switch (introSequence.Value) { case IntroSequence.Welcome: @@ -106,12 +118,6 @@ namespace osu.Game.Screens.Backgrounds else newBackground = new Background(backgroundName); - if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Always - || seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && seasonalBackgroundLoader.IsInSeason) - { - newBackground = seasonalBackgroundLoader.LoadBackground() ?? newBackground; - } - newBackground.Depth = currentDisplay; return newBackground; From 51a58269add518f7ea68f590fceeff11612cc5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 17:57:29 +0100 Subject: [PATCH 292/322] Fix nullref in case of successfull request but no backgrounds available --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index c884756c80..daceb05fd7 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.Backgrounds request.Success += response => { cachedResponse.Value = response; - current = RNG.Next(0, cachedResponse.Value.Backgrounds.Count); + current = RNG.Next(0, response.Backgrounds?.Count ?? 0); }; api.PerformAsync(request); @@ -41,7 +41,7 @@ namespace osu.Game.Graphics.Backgrounds public SeasonalBackground LoadBackground() { var backgrounds = cachedResponse.Value.Backgrounds; - if (!backgrounds.Any()) return null; + if (backgrounds == null || !backgrounds.Any()) return null; current = (current + 1) % backgrounds.Count; string url = backgrounds[current].Url; From d5dfd1dffeee753cb47d70afc87a1b3055e1962e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Fri, 30 Oct 2020 18:07:07 +0100 Subject: [PATCH 293/322] Insert optional parentheses --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index b65b45060f..45374e1223 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Backgrounds string backgroundName; if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Always - || seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && seasonalBackgroundLoader.IsInSeason) + || (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && seasonalBackgroundLoader.IsInSeason)) { var seasonalBackground = seasonalBackgroundLoader.LoadBackground(); From 82ef85569bffe30e64a00414ddb8465348d645d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 19:35:08 +0100 Subject: [PATCH 294/322] Fix nullref when querying IsInSeason before request completion --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index daceb05fd7..2963d57a97 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.Backgrounds return new SeasonalBackground(url); } - public bool IsInSeason => DateTimeOffset.Now < cachedResponse.Value.EndDate; + public bool IsInSeason => cachedResponse.Value != null && DateTimeOffset.Now < cachedResponse.Value.EndDate; } [LongRunningLoad] From 20c27c69431eb680781338c4e0deba1f2a1658ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 19:55:17 +0100 Subject: [PATCH 295/322] Rename lookup & field --- osu.Game/Configuration/SessionStatics.cs | 4 ++-- .../Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 199889391b..c470058ae8 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -14,7 +14,7 @@ namespace osu.Game.Configuration { Set(Static.LoginOverlayDisplayed, false); Set(Static.MutedAudioNotificationShownOnce, false); - Set(Static.SeasonalBackgroundsResponse, null); + Set(Static.SeasonalBackgrounds, null); } } @@ -22,6 +22,6 @@ namespace osu.Game.Configuration { LoginOverlayDisplayed, MutedAudioNotificationShownOnce, - SeasonalBackgroundsResponse, + SeasonalBackgrounds, } } diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index 2963d57a97..1c38e67451 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -18,20 +18,20 @@ namespace osu.Game.Graphics.Backgrounds [LongRunningLoad] public class SeasonalBackgroundLoader : Component { - private Bindable cachedResponse; + private Bindable seasonalBackgrounds; private int current; [BackgroundDependencyLoader] private void load(SessionStatics sessionStatics, IAPIProvider api) { - cachedResponse = sessionStatics.GetBindable(Static.SeasonalBackgroundsResponse); + seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); - if (cachedResponse.Value != null) return; + if (seasonalBackgrounds.Value != null) return; var request = new GetSeasonalBackgroundsRequest(); request.Success += response => { - cachedResponse.Value = response; + seasonalBackgrounds.Value = response; current = RNG.Next(0, response.Backgrounds?.Count ?? 0); }; @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Backgrounds public SeasonalBackground LoadBackground() { - var backgrounds = cachedResponse.Value.Backgrounds; + var backgrounds = seasonalBackgrounds.Value.Backgrounds; if (backgrounds == null || !backgrounds.Any()) return null; current = (current + 1) % backgrounds.Count; @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.Backgrounds return new SeasonalBackground(url); } - public bool IsInSeason => cachedResponse.Value != null && DateTimeOffset.Now < cachedResponse.Value.EndDate; + public bool IsInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate; } [LongRunningLoad] From aeab2be5d1968c7a1a6a712afc2897033891bb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 19:56:52 +0100 Subject: [PATCH 296/322] Add xmldoc to SeasonalBackgroundMode --- osu.Game/Configuration/SeasonalBackgroundMode.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Configuration/SeasonalBackgroundMode.cs b/osu.Game/Configuration/SeasonalBackgroundMode.cs index 406736b2a4..6ef835ce5f 100644 --- a/osu.Game/Configuration/SeasonalBackgroundMode.cs +++ b/osu.Game/Configuration/SeasonalBackgroundMode.cs @@ -5,8 +5,19 @@ namespace osu.Game.Configuration { public enum SeasonalBackgroundMode { + /// + /// Seasonal backgrounds are shown regardless of season, if at all available. + /// Always, + + /// + /// Seasonal backgrounds are shown only during their corresponding season. + /// Sometimes, + + /// + /// Seasonal backgrounds are never shown. + /// Never } } From cf0e8e0a620faaa77ba490ab3e71b9901eabd658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 19:59:52 +0100 Subject: [PATCH 297/322] Document nullability of seasonal backgrounds --- osu.Game/Configuration/SessionStatics.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index c470058ae8..03bc434aac 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -22,6 +22,11 @@ namespace osu.Game.Configuration { LoginOverlayDisplayed, MutedAudioNotificationShownOnce, + + /// + /// Info about seasonal backgrounds available fetched from API - see . + /// Value under this lookup can be null if there are no backgrounds available (or API is not reachable). + /// SeasonalBackgrounds, } } From 67a325f47dd608f625711a4e38b8968ee514716e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 20:32:14 +0100 Subject: [PATCH 298/322] Move config setting logic to background loader --- .../Backgrounds/SeasonalBackgroundLoader.cs | 17 +++++++++++++---- .../Backgrounds/BackgroundScreenDefault.cs | 14 +++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index 1c38e67451..a9b9929c79 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -18,12 +18,14 @@ namespace osu.Game.Graphics.Backgrounds [LongRunningLoad] public class SeasonalBackgroundLoader : Component { + private Bindable seasonalBackgroundMode; private Bindable seasonalBackgrounds; private int current; [BackgroundDependencyLoader] - private void load(SessionStatics sessionStatics, IAPIProvider api) + private void load(OsuConfigManager config, SessionStatics sessionStatics, IAPIProvider api) { + seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); if (seasonalBackgrounds.Value != null) return; @@ -38,10 +40,17 @@ namespace osu.Game.Graphics.Backgrounds api.PerformAsync(request); } - public SeasonalBackground LoadBackground() + public SeasonalBackground LoadNextBackground() { + if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never + || (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason)) + { + return null; + } + var backgrounds = seasonalBackgrounds.Value.Backgrounds; - if (backgrounds == null || !backgrounds.Any()) return null; + if (backgrounds == null || !backgrounds.Any()) + return null; current = (current + 1) % backgrounds.Count; string url = backgrounds[current].Url; @@ -49,7 +58,7 @@ namespace osu.Game.Graphics.Backgrounds return new SeasonalBackground(url); } - public bool IsInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate; + private bool isInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate; } [LongRunningLoad] diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 45374e1223..cbe0841537 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -79,16 +79,12 @@ namespace osu.Game.Screens.Backgrounds Background newBackground; string backgroundName; - if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Always - || (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && seasonalBackgroundLoader.IsInSeason)) - { - var seasonalBackground = seasonalBackgroundLoader.LoadBackground(); + var seasonalBackground = seasonalBackgroundLoader.LoadNextBackground(); - if (seasonalBackground != null) - { - seasonalBackground.Depth = currentDisplay; - return seasonalBackground; - } + if (seasonalBackground != null) + { + seasonalBackground.Depth = currentDisplay; + return seasonalBackground; } switch (introSequence.Value) From 8632f0d77f18b6fb65aee838bced622aba6bdc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 21:24:20 +0100 Subject: [PATCH 299/322] Add test coverage --- .../TestSceneSeasonalBackgroundLoader.cs | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs new file mode 100644 index 0000000000..8f5990aeb1 --- /dev/null +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -0,0 +1,200 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Tests.Visual.Background +{ + public class TestSceneSeasonalBackgroundLoader : ScreenTestScene + { + [Resolved] + private OsuConfigManager config { get; set; } + + [Resolved] + private SessionStatics statics { get; set; } + + [Cached(typeof(LargeTextureStore))] + private LookupLoggingTextureStore textureStore = new LookupLoggingTextureStore(); + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + private SeasonalBackgroundLoader backgroundLoader; + private Container backgroundContainer; + + // in real usages these would be online URLs, but correct execution of this test + // shouldn't be coupled to existence of online assets. + private static readonly List seasonal_background_urls = new List + { + "Backgrounds/bg2", + "Backgrounds/bg4", + "Backgrounds/bg3" + }; + + [BackgroundDependencyLoader] + private void load(LargeTextureStore wrappedStore) + { + textureStore.AddStore(wrappedStore); + + Add(backgroundContainer = new Container + { + RelativeSizeAxes = Axes.Both + }); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + // reset API response in statics to avoid test crosstalk. + statics.Set(Static.SeasonalBackgrounds, null); + textureStore.PerformedLookups.Clear(); + dummyAPI.SetState(APIState.Online); + + backgroundContainer.Clear(); + }); + + [TestCase(-5)] + [TestCase(5)] + public void TestAlwaysSeasonal(int daysOffset) + { + registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset)); + setSeasonalBackgroundMode(SeasonalBackgroundMode.Always); + + createLoader(); + + for (int i = 0; i < 4; ++i) + loadNextBackground(); + + AddAssert("all backgrounds cycled", () => new HashSet(textureStore.PerformedLookups).SetEquals(seasonal_background_urls)); + } + + [TestCase(-5)] + [TestCase(5)] + public void TestNeverSeasonal(int daysOffset) + { + registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset)); + setSeasonalBackgroundMode(SeasonalBackgroundMode.Never); + + createLoader(); + + assertNoBackgrounds(); + } + + [Test] + public void TestSometimesInSeason() + { + registerBackgroundsResponse(DateTimeOffset.Now.AddDays(5)); + setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes); + + createLoader(); + + assertAnyBackground(); + } + + [Test] + public void TestSometimesOutOfSeason() + { + registerBackgroundsResponse(DateTimeOffset.Now.AddDays(-10)); + setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes); + + createLoader(); + + assertNoBackgrounds(); + } + + [Test] + public void TestDelayedConnectivity() + { + registerBackgroundsResponse(DateTimeOffset.Now.AddDays(30)); + setSeasonalBackgroundMode(SeasonalBackgroundMode.Always); + AddStep("go offline", () => dummyAPI.SetState(APIState.Offline)); + + createLoader(); + assertNoBackgrounds(); + + AddStep("go online", () => dummyAPI.SetState(APIState.Online)); + + assertAnyBackground(); + } + + private void registerBackgroundsResponse(DateTimeOffset endDate) + => AddStep("setup request handler", () => + { + dummyAPI.HandleRequest = request => + { + if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest)) + return; + + backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds + { + Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(), + EndDate = endDate + }); + }; + }); + + private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode) + => AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode)); + + private void createLoader() + { + AddStep("create loader", () => + { + if (backgroundLoader != null) + Remove(backgroundLoader); + + LoadComponentAsync(backgroundLoader = new SeasonalBackgroundLoader(), Add); + }); + + AddUntilStep("wait for loaded", () => backgroundLoader.IsLoaded); + } + + private void loadNextBackground() + { + SeasonalBackground background = null; + + AddStep("create next background", () => + { + background = backgroundLoader.LoadNextBackground(); + LoadComponentAsync(background, bg => backgroundContainer.Child = bg); + }); + + AddUntilStep("background loaded", () => background.IsLoaded); + } + + private void assertAnyBackground() + { + loadNextBackground(); + AddAssert("background looked up", () => textureStore.PerformedLookups.Any()); + } + + private void assertNoBackgrounds() + { + AddAssert("no background available", () => backgroundLoader.LoadNextBackground() == null); + AddAssert("no lookups performed", () => !textureStore.PerformedLookups.Any()); + } + + private class LookupLoggingTextureStore : LargeTextureStore + { + public List PerformedLookups { get; } = new List(); + + public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) + { + PerformedLookups.Add(name); + return base.Get(name, wrapModeS, wrapModeT); + } + } + } +} From 29ad09990259245b8b828a49add4411a90d5f88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 21:49:14 +0100 Subject: [PATCH 300/322] Allow to fetch if going online after launch --- .../Backgrounds/SeasonalBackgroundLoader.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index a9b9929c79..ff290dd99e 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -18,17 +18,29 @@ namespace osu.Game.Graphics.Backgrounds [LongRunningLoad] public class SeasonalBackgroundLoader : Component { + [Resolved] + private IAPIProvider api { get; set; } + + private readonly IBindable apiState = new Bindable(); private Bindable seasonalBackgroundMode; private Bindable seasonalBackgrounds; + private int current; [BackgroundDependencyLoader] - private void load(OsuConfigManager config, SessionStatics sessionStatics, IAPIProvider api) + private void load(OsuConfigManager config, SessionStatics sessionStatics) { seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); - if (seasonalBackgrounds.Value != null) return; + apiState.BindTo(api.State); + apiState.BindValueChanged(fetchSeasonalBackgrounds, true); + } + + private void fetchSeasonalBackgrounds(ValueChangedEvent stateChanged) + { + if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online) + return; var request = new GetSeasonalBackgroundsRequest(); request.Success += response => @@ -48,7 +60,7 @@ namespace osu.Game.Graphics.Backgrounds return null; } - var backgrounds = seasonalBackgrounds.Value.Backgrounds; + var backgrounds = seasonalBackgrounds.Value?.Backgrounds; if (backgrounds == null || !backgrounds.Any()) return null; From 38cf90a69b8734343b68fce3646b618f4d3ad6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 22:03:26 +0100 Subject: [PATCH 301/322] Change background to seasonal right after login --- .../Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 9 +++++++++ osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index ff290dd99e..b439d98083 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -18,6 +18,12 @@ namespace osu.Game.Graphics.Backgrounds [LongRunningLoad] public class SeasonalBackgroundLoader : Component { + /// + /// Fired when background change should be changed due to receiving backgrounds from API + /// or when the user setting is changed (as it might require unloading the seasonal background). + /// + public event Action SeasonalBackgroundChanged; + [Resolved] private IAPIProvider api { get; set; } @@ -31,7 +37,10 @@ namespace osu.Game.Graphics.Backgrounds private void load(OsuConfigManager config, SessionStatics sessionStatics) { seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); + seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); + seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); + seasonalBackgrounds.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); apiState.BindTo(api.State); apiState.BindValueChanged(fetchSeasonalBackgrounds, true); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index cbe0841537..f392386bf9 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -25,7 +25,6 @@ namespace osu.Game.Screens.Backgrounds private Bindable skin; private Bindable mode; private Bindable introSequence; - private Bindable seasonalBackgroundMode; private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); [Resolved] @@ -43,14 +42,13 @@ namespace osu.Game.Screens.Backgrounds skin = skinManager.CurrentSkin.GetBoundCopy(); mode = config.GetBindable(OsuSetting.MenuBackgroundSource); introSequence = config.GetBindable(OsuSetting.IntroSequence); - seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); mode.ValueChanged += _ => Next(); beatmap.ValueChanged += _ => Next(); introSequence.ValueChanged += _ => Next(); - seasonalBackgroundMode.ValueChanged += _ => Next(); + seasonalBackgroundLoader.SeasonalBackgroundChanged += Next; currentDisplay = RNG.Next(0, background_count); From 391dd73843b52fcf559b6b23eb56171c89e7c37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 22:39:34 +0100 Subject: [PATCH 302/322] Fix typo in comment --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index b439d98083..03b7300011 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Backgrounds public class SeasonalBackgroundLoader : Component { /// - /// Fired when background change should be changed due to receiving backgrounds from API + /// Fired when background should be changed due to receiving backgrounds from API /// or when the user setting is changed (as it might require unloading the seasonal background). /// public event Action SeasonalBackgroundChanged; From 78842ab95ae90862c704ee3c350cd08b8c6126e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Oct 2020 22:40:24 +0100 Subject: [PATCH 303/322] Improve look & behaviour of background transitions --- .../Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 2 ++ osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index 03b7300011..99f3a8a6e8 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -97,6 +97,8 @@ namespace osu.Game.Graphics.Backgrounds private void load(LargeTextureStore textures) { Sprite.Texture = textures.Get(url) ?? textures.Get(fallback_texture_name); + // ensure we're not loading in without a transition. + this.FadeInFromZero(200, Easing.InOutSine); } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index f392386bf9..3f6210310f 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.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.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -52,7 +53,8 @@ namespace osu.Game.Screens.Backgrounds currentDisplay = RNG.Next(0, background_count); - LoadComponentAsync(seasonalBackgroundLoader, _ => LoadComponentAsync(createBackground(), display)); + LoadComponentAsync(seasonalBackgroundLoader); + Next(); } private void display(Background newBackground) @@ -65,11 +67,14 @@ namespace osu.Game.Screens.Backgrounds } private ScheduledDelegate nextTask; + private CancellationTokenSource cancellationTokenSource; public void Next() { nextTask?.Cancel(); - nextTask = Scheduler.AddDelayed(() => { LoadComponentAsync(createBackground(), display); }, 100); + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + nextTask = Scheduler.AddDelayed(() => LoadComponentAsync(createBackground(), display, cancellationTokenSource.Token), 100); } private Background createBackground() From 6a293dd536a9444a52ffd3de7c4992256e04bf64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Oct 2020 18:56:30 +0900 Subject: [PATCH 304/322] Add missing ctor parameters back --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 1e87893f39..2af15923a0 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) + new AccuracyCircle(score, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index cb4560802b..711763330c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score) + Child = new AccuracyCircle(score, withFlair) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 129b1bc6d3dfcebb51e4e30a7f40d34b6dea9807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Oct 2020 11:35:25 +0100 Subject: [PATCH 305/322] Delete all selected objects if shift-clicked on one --- .../Edit/Compose/Components/SelectionHandler.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 01e23bafc5..92c75eae4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -226,13 +226,21 @@ namespace osu.Game.Screens.Edit.Compose.Components internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) - EditorBeatmap.Remove(blueprint.HitObject); + handleQuickDeletion(blueprint); else if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left)) blueprint.ToggleSelection(); else ensureSelected(blueprint); } + private void handleQuickDeletion(SelectionBlueprint blueprint) + { + if (!blueprint.IsSelected) + EditorBeatmap.Remove(blueprint.HitObject); + else + deleteSelected(); + } + private void ensureSelected(SelectionBlueprint blueprint) { if (blueprint.IsSelected) From 003994ab7518cf821204a5ba417c9b9bb4c35ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Oct 2020 12:21:07 +0100 Subject: [PATCH 306/322] Bind UpdateVisibility() directly to source of truth --- .../Edit/Compose/Components/BlueprintContainer.cs | 9 +-------- .../Edit/Compose/Components/SelectionHandler.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 5ac360d029..fa98358dbe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -210,10 +210,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } if (DragBox.State == Visibility.Visible) - { DragBox.Hide(); - SelectionHandler.UpdateVisibility(); - } } protected override bool OnKeyDown(KeyDownEvent e) @@ -352,11 +349,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Selects all s. /// - private void selectAll() - { - SelectionBlueprints.ToList().ForEach(m => m.Select()); - SelectionHandler.UpdateVisibility(); - } + private void selectAll() => SelectionBlueprints.ToList().ForEach(m => m.Select()); /// /// Deselects all selected s. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 01e23bafc5..07ae283667 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -201,8 +201,6 @@ namespace osu.Game.Screens.Edit.Compose.Components // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject)) EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); - - UpdateVisibility(); } /// @@ -214,8 +212,6 @@ namespace osu.Game.Screens.Edit.Compose.Components selectedBlueprints.Remove(blueprint); EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); - - UpdateVisibility(); } /// @@ -254,7 +250,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Updates whether this is visible. /// - internal void UpdateVisibility() + private void updateVisibility() { int count = selectedBlueprints.Count; @@ -421,7 +417,11 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => UpdateTernaryStates(); - EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => UpdateTernaryStates(); + EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => + { + updateVisibility(); + UpdateTernaryStates(); + }; } /// From 3322b8a7ea03d97e3c18acf58965d0c7798d4ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Oct 2020 12:25:02 +0100 Subject: [PATCH 307/322] Run OnSelectionChanged() on each change --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 07ae283667..0547b15e3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -257,16 +257,15 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; if (count > 0) - { Show(); - OnSelectionChanged(); - } else Hide(); + + OnSelectionChanged(); } /// - /// Triggered whenever more than one object is selected, on each change. + /// Triggered whenever the set of selected objects changes. /// Should update the selection box's state to match supported operations. /// protected virtual void OnSelectionChanged() From d74c19e2d703f6e57139727692a3473ea7bd55fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Oct 2020 12:28:35 +0100 Subject: [PATCH 308/322] Shorten show/hide code --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 0547b15e3d..41098cc84c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -256,11 +256,7 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; - if (count > 0) - Show(); - else - Hide(); - + this.FadeTo(count > 0 ? 1 : 0); OnSelectionChanged(); } From 007c27d3ffa987afdfc5c3502c27d4a89f8538fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Oct 2020 14:45:11 +0100 Subject: [PATCH 309/322] Schedule visibility update once per frame --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 41098cc84c..5c1b41d848 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -414,7 +414,7 @@ namespace osu.Game.Screens.Edit.Compose.Components EditorBeatmap.HitObjectUpdated += _ => UpdateTernaryStates(); EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => { - updateVisibility(); + Scheduler.AddOnce(updateVisibility); UpdateTernaryStates(); }; } From a9a3489e92b200d99c335cd52aab2c93e4cf3a17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Oct 2020 22:51:31 +0900 Subject: [PATCH 310/322] Fix potential null reference when loading background As seen in https://discordapp.com/channels/188630481301012481/188630652340404224/772094427342569493. Caused due to async load of the loader, which means it may not be ready before Next() is called. --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 1 - osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index 99f3a8a6e8..a48da37804 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Graphics.Backgrounds { - [LongRunningLoad] public class SeasonalBackgroundLoader : Component { /// diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 3f6210310f..8beb955824 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Backgrounds mode = config.GetBindable(OsuSetting.MenuBackgroundSource); introSequence = config.GetBindable(OsuSetting.IntroSequence); + AddInternal(seasonalBackgroundLoader); + user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); mode.ValueChanged += _ => Next(); @@ -53,7 +55,6 @@ namespace osu.Game.Screens.Backgrounds currentDisplay = RNG.Next(0, background_count); - LoadComponentAsync(seasonalBackgroundLoader); Next(); } From 2065680e9d1d12183c5493dfc639fff9b74ee97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Oct 2020 17:01:45 +0100 Subject: [PATCH 311/322] Simplify test case --- .../Background/TestSceneSeasonalBackgroundLoader.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index 8f5990aeb1..fba0d92d4b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -149,18 +149,14 @@ namespace osu.Game.Tests.Visual.Background => AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode)); private void createLoader() - { - AddStep("create loader", () => + => AddStep("create loader", () => { if (backgroundLoader != null) Remove(backgroundLoader); - LoadComponentAsync(backgroundLoader = new SeasonalBackgroundLoader(), Add); + Add(backgroundLoader = new SeasonalBackgroundLoader()); }); - AddUntilStep("wait for loaded", () => backgroundLoader.IsLoaded); - } - private void loadNextBackground() { SeasonalBackground background = null; From 8a54fdd4e6427b079f8d1f45205270d1f487b007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Nov 2020 14:25:33 +0100 Subject: [PATCH 312/322] Ensure LoadOszIntoOsu returns actual imported map --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 80fbda8e1d..b941313103 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -821,15 +821,13 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - await manager.Import(temp); - - var imported = manager.GetAllUsableBeatmapSets(); + var importedSet = await manager.Import(temp); ensureLoaded(osu); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return imported.LastOrDefault(); + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); } private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) From 6ff13e399ad66d7bf898630efafb8851e625c688 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 17:14:13 +0000 Subject: [PATCH 313/322] Bump Microsoft.CodeAnalysis.FxCopAnalyzers from 3.0.0 to 3.3.1 Bumps [Microsoft.CodeAnalysis.FxCopAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 3.0.0 to 3.3.1. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/master/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v3.0.0...v3.3.1) Signed-off-by: dependabot-preview[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2d3478f256..186b2049c6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ - + $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From 6e9ed76251ea86fbc6f216953d964ae0ccc61f62 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 17:14:57 +0000 Subject: [PATCH 314/322] Bump Microsoft.Build.Traversal from 2.1.1 to 2.2.3 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.1.1 to 2.2.3. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.1.1...Microsoft.Build.Traversal.2.2.3) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index a9a531f59c..10b61047ac 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.1.1" + "Microsoft.Build.Traversal": "2.2.3" } } \ No newline at end of file From 79e610d31b9017e9d13e82fd704a32fc72c5e768 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 17:15:23 +0000 Subject: [PATCH 315/322] Bump Microsoft.CodeAnalysis.BannedApiAnalyzers from 3.3.0 to 3.3.1 Bumps [Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 3.3.0 to 3.3.1. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/master/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v3.3.0...v3.3.1) Signed-off-by: dependabot-preview[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2d3478f256..056216f14b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + From 2b0bea535efa58df11a2ac216742a2a8521e3e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Nov 2020 18:47:40 +0100 Subject: [PATCH 316/322] Resolve CA1805 inspections "Member is explicitly initialized to its default value" --- osu.Game/Database/DatabaseWriteUsage.cs | 2 +- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 2 +- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs index 1fd2f23d50..ddafd77066 100644 --- a/osu.Game/Database/DatabaseWriteUsage.cs +++ b/osu.Game/Database/DatabaseWriteUsage.cs @@ -26,7 +26,7 @@ namespace osu.Game.Database /// Whether this write usage will commit a transaction on completion. /// If false, there is a parent usage responsible for transaction commit. /// - public bool IsTransactionLeader = false; + public bool IsTransactionLeader; protected void Dispose(bool disposing) { diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index ed5c73bee6..b9122d254d 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers /// Allows controlling the scroll bar from any position in the container using the right mouse button. /// Uses the value of to smoothly scroll to the dragged location. /// - public bool RightMouseScrollbar = false; + public bool RightMouseScrollbar; /// /// Controls the rate with which the target position is approached when performing a relative drag. Default is 0.02. diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index cf5c88b8fd..b671f4c68c 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Replays /// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data. /// Disabling this can make replay playback smoother (useful for autoplay, currently). /// - public bool FrameAccuratePlayback = false; + public bool FrameAccuratePlayback; protected bool HasFrames => Frames.Count > 0; diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index aa3bd2e4b7..088e997de9 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual /// /// Whether custom test steps are provided. Custom tests should invoke to create the test steps. /// - protected virtual bool HasCustomSteps { get; } = false; + protected virtual bool HasCustomSteps => false; protected TestPlayer Player; From ca5de22ca5d8047424958eab6975b17056917055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Nov 2020 18:49:11 +0100 Subject: [PATCH 317/322] Resolve CA1834 inspection "Use `StringBuilder.Append(char)` instead of `StringBuilder.Append(string)` when the input is a constant unit string" --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 80a4d6dea4..80fd6c22bb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -417,7 +417,7 @@ namespace osu.Game.Beatmaps.Formats string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty; int volume = samples.FirstOrDefault()?.Volume ?? 100; - sb.Append(":"); + sb.Append(':'); sb.Append(FormattableString.Invariant($"{customSampleBank}:")); sb.Append(FormattableString.Invariant($"{volume}:")); sb.Append(FormattableString.Invariant($"{sampleFilename}")); From 89bf7b1bd669d83e57b6f299b0489e8cd950b1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Nov 2020 18:51:39 +0100 Subject: [PATCH 318/322] Resolve CA1835 inspection "Change the `ReadAsync` method call to use the `Stream.ReadAsync(Memory, CancellationToken)` overload" --- osu.Game/IO/Archives/ArchiveReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index a30f961daf..f74574e60c 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -41,7 +41,7 @@ namespace osu.Game.IO.Archives return null; byte[] buffer = new byte[input.Length]; - await input.ReadAsync(buffer, 0, buffer.Length); + await input.ReadAsync(buffer); return buffer; } } From 3090b6ccb5eed7b77868cf508b2cf48832f6d0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Nov 2020 18:54:44 +0100 Subject: [PATCH 319/322] Resolve CA2249 inspections "Use `string.Contains` instead of `string.IndexOf` to improve readability" --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index e2550d1ca4..8d8ca523d5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -347,7 +347,7 @@ namespace osu.Game.Beatmaps.Formats /// The line which may contains variables. private void decodeVariables(ref string line) { - while (line.IndexOf('$') >= 0) + while (line.Contains('$')) { string origLine = line; diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 60c6aa1d8a..c7c37cbc0d 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); if (!string.IsNullOrEmpty(criteria.SearchString)) - matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); + matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); r.MatchingFilter = matchingFilter; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index dce4028f17..1aab50037a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Select.Carousel var terms = Beatmap.SearchableTerms; foreach (var criteriaTerm in criteria.SearchTerms) - match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); + match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase)); // if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs. // this should be done after text matching so we can prioritise matching numbers in metadata. diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index f34f8f6505..7bddb3e51b 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Select if (string.IsNullOrEmpty(value)) return false; - return value.IndexOf(SearchTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; + return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); } public string SearchTerm; From 164370bc7da7ed6dfe08507eec0fbafc32cffd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Nov 2020 20:51:23 +0100 Subject: [PATCH 320/322] Resolve more CA1805 inspections --- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 7 +++++-- osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs | 4 ++-- .../Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 937473e824..6841ecd23c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Osu.Mods /// private const float target_clamp = 1; - private readonly float targetBreakMultiplier = 0; - private readonly float easing = 1; + private readonly float targetBreakMultiplier; + private readonly float easing; private readonly CompositeDrawable restrictTo; @@ -86,6 +86,9 @@ namespace osu.Game.Rulesets.Osu.Mods { this.restrictTo = restrictTo; this.beatmap = beatmap; + + targetBreakMultiplier = 0; + easing = 1; } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 58cc324233..de46f9d1cf 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -81,8 +81,8 @@ namespace osu.Game.Tests.Gameplay private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation { - public bool NewCombo { get; set; } = false; - public int ComboOffset { get; } = 0; + public bool NewCombo { get; set; } + public int ComboOffset => 0; public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs index fdc20dc477..07ff56b5c3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public Bindable InitialRoomsReceived { get; } = new Bindable(true); - public IBindableList Rooms { get; } = null; + public IBindableList Rooms => null; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index e02ebf3be1..0b2c0ce63b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -197,8 +197,8 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestHitObject : ConvertHitObject, IHasPosition { - public float X { get; } = 0; - public float Y { get; } = 0; + public float X => 0; + public float Y => 0; public Vector2 Position { get; } = Vector2.Zero; } } From 432282e8de8fb62a50fd2fef4de3f77047889e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Nov 2020 21:22:04 +0100 Subject: [PATCH 321/322] Use alternative solution to avoid storing last zoom --- .../Timeline/TimelineBlueprintContainer.cs | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 008da14a21..10913a8bb9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -9,10 +9,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; -using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled = handleScrollViaDrag }; - protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect, this); + protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); private void handleScrollViaDrag(DragEvent e) { @@ -137,17 +137,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private class TimelineDragBox : DragBox { - private Vector2 lastMouseDown; + // the following values hold the start and end X positions of the drag box in the timeline's local space, + // but with zoom unapplied in order to be able to compensate for positional changes + // while the timeline is being zoomed in/out. + private float? selectionStart; + private float selectionEnd; - private float? lastZoom; - private float localMouseDown; + [Resolved] + private Timeline timeline { get; set; } - private readonly TimelineBlueprintContainer parent; - - public TimelineDragBox(Action performSelect, TimelineBlueprintContainer parent) + public TimelineDragBox(Action performSelect) : base(performSelect) { - this.parent = parent; } protected override Drawable CreateBox() => new Box @@ -158,27 +159,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override bool HandleDrag(MouseButtonEvent e) { - // store the original position of the mouse down, as we may be scrolled during selection. - if (lastMouseDown != e.ScreenSpaceMouseDownPosition) - { - lastMouseDown = e.ScreenSpaceMouseDownPosition; - localMouseDown = e.MouseDownPosition.X; - lastZoom = null; - } + selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom; - //Zooming the timeline shifts the coordinate system. zoomCorrection compensates for that - float zoomCorrection = lastZoom.HasValue ? (parent.timeline.CurrentZoom / lastZoom.Value) : 1; - localMouseDown *= zoomCorrection; - lastZoom = parent.timeline.CurrentZoom; + // only calculate end when a transition is not in progress to avoid bouncing. + if (Precision.AlmostEquals(timeline.CurrentZoom, timeline.Zoom)) + selectionEnd = e.MousePosition.X / timeline.CurrentZoom; - float selection1 = localMouseDown; - float selection2 = e.MousePosition.X * zoomCorrection; + updateDragBoxPosition(); + return true; + } - Box.X = Math.Min(selection1, selection2); - Box.Width = Math.Abs(selection1 - selection2); + private void updateDragBoxPosition() + { + if (selectionStart == null) + return; + + float rescaledStart = selectionStart.Value * timeline.CurrentZoom; + float rescaledEnd = selectionEnd * timeline.CurrentZoom; + + Box.X = Math.Min(rescaledStart, rescaledEnd); + Box.Width = Math.Abs(rescaledStart - rescaledEnd); PerformSelection?.Invoke(Box.ScreenSpaceDrawQuad.AABBFloat); - return true; + } + + public override void Hide() + { + base.Hide(); + selectionStart = null; } } From 71d55f16f3853f3e9f3cc56bb4cf981d957e9840 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 1 Nov 2020 13:50:38 -0800 Subject: [PATCH 322/322] Fix edit beatmap options button not resuming back to song select --- osu.Game/Screens/Select/PlaySongSelect.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 19769f487d..ee8825640c 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -32,11 +32,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuColour colours) { - BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => - { - ValidForResume = false; - Edit(); - }); + BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => Edit()); ((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += PresentScore; }