From 98aaf83177ee1cb8777619936e31926b2167b399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 15:57:37 +0900 Subject: [PATCH 01/17] Add a centralised constant for the osu URL schema protocol --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 12 ++++++------ osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 4 ++-- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- osu.Game/OsuGameBase.cs | 2 ++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 8def8005f1..cea4d510c1 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -409,26 +409,26 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(2, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); - Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#japanese", result.Links[1].Url); } [Test] public void TestOsuProtocol() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." }); + Message result = MessageFormatter.FormatMessage(new Message { Content = $"This is a custom protocol {OsuGameBase.OSU_PROTOCOL}chan/#english." }); Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); Assert.AreEqual(26, result.Links[0].Index); Assert.AreEqual(19, result.Links[0].Length); - result = MessageFormatter.FormatMessage(new Message { Content = "This is a [custom protocol](osu://chan/#english)." }); + result = MessageFormatter.FormatMessage(new Message { Content = $"This is a [custom protocol]({OsuGameBase.OSU_PROTOCOL}chan/#english)." }); Assert.AreEqual("This is a custom protocol.", result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); Assert.AreEqual("#english", result.Links[0].Argument); Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(15, result.Links[0].Length); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 12b5f64559..d077868175 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -87,8 +87,8 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d7974004b1..4c477d58b6 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -269,10 +269,10 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(time_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels - handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel); + handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); string empty = ""; while (space-- > 0) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0b2644d5ba..86390e7630 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -52,6 +52,8 @@ namespace osu.Game /// public partial class OsuGameBase : Framework.Game, ICanAcceptFiles { + public const string OSU_PROTOCOL = "osu://"; + public const string CLIENT_STREAM_NAME = @"lazer"; public const int SAMPLE_CONCURRENCY = 6; From 29c5683ba3ce2e7de362cac68949b3a46424cd12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:06:38 +0900 Subject: [PATCH 02/17] Add handling of beatmap links on startup --- .../TestSceneStartupBeatmapDisplay.cs | 22 ++++++++++++ .../TestSceneStartupBeatmapSetDisplay.cs | 22 ++++++++++++ osu.Game/OsuGame.cs | 36 ++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs new file mode 100644 index 0000000000..1efa24435e --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneStartupBeatmapDisplay : OsuGameTestScene + { + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://b/75" }); + + [Test] + public void TestBeatmapLink() + { + AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + } + } +} diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs new file mode 100644 index 0000000000..1339c514e4 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneStartupBeatmapSetDisplay : OsuGameTestScene + { + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://s/1" }); + + [Test] + public void TestBeatmapSetLink() + { + AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5b58dec0c3..390e96d768 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -150,6 +150,7 @@ namespace osu.Game protected SettingsOverlay Settings; private VolumeOverlay volume; + private OsuLogo osuLogo; private MainMenu menuScreen; @@ -898,8 +899,41 @@ namespace osu.Game if (args?.Length > 0) { string[] paths = args.Where(a => !a.StartsWith('-')).ToArray(); + if (paths.Length > 0) - Task.Run(() => Import(paths)); + { + string firstPath = paths.First(); + + if (firstPath.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) + { + handleOsuProtocolUrl(firstPath); + } + else + { + Task.Run(() => Import(paths)); + } + } + } + } + + private void handleOsuProtocolUrl(string url) + { + if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) + throw new ArgumentException("Invalid osu URL provided.", nameof(url)); + + string[] pieces = url.Split('/'); + + switch (pieces[2]) + { + case "s": + if (int.TryParse(pieces[3], out int beatmapSetId)) + ShowBeatmapSet(beatmapSetId); + break; + + case "b": + if (int.TryParse(pieces[3], out int beatmapId)) + ShowBeatmap(beatmapId); + break; } } From cd3641137bc920ef042396125c0c323fd7875fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:02:54 +0100 Subject: [PATCH 03/17] Add `OsuColour` method mapping colours from basic theme to mod types --- osu.Game/Graphics/OsuColour.cs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 886ba7ef92..afedf36cad 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Utils; @@ -157,6 +158,36 @@ namespace osu.Game.Graphics } } + /// + /// Retrieves the main accent colour for a . + /// + public Color4 ForModType(ModType modType) + { + switch (modType) + { + case ModType.Automation: + return Blue1; + + case ModType.DifficultyIncrease: + return Red1; + + case ModType.DifficultyReduction: + return Lime1; + + case ModType.Conversion: + return Purple1; + + case ModType.Fun: + return Pink1; + + case ModType.System: + return Gray7; + + default: + throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type"); + } + } + /// /// Returns a foreground text colour that is supposed to contrast well with /// the supplied . From 5186693dad42b370b115da9a304b3fc7873470a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:03:07 +0100 Subject: [PATCH 04/17] Implement tiny mod switch --- .../UserInterface/TestSceneModSwitchTiny.cs | 85 +++++++++++++++++ osu.Game/Rulesets/UI/ModSwitchTiny.cs | 91 +++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs create mode 100644 osu.Game/Rulesets/UI/ModSwitchTiny.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs new file mode 100644 index 0000000000..dbde7ce425 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSwitchTiny : OsuTestScene + { + [Test] + public void TestOsu() => createSwitchTestFor(new OsuRuleset()); + + [Test] + public void TestTaiko() => createSwitchTestFor(new TaikoRuleset()); + + [Test] + public void TestCatch() => createSwitchTestFor(new CatchRuleset()); + + [Test] + public void TestMania() => createSwitchTestFor(new ManiaRuleset()); + + private void createSwitchTestFor(Ruleset ruleset) + { + AddStep("no colour scheme", () => Child = createContent(ruleset, null)); + + foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast()) + { + AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme)); + } + + AddToggleStep("toggle active", active => this.ChildrenOfType().ForEach(s => s.Active.Value = active)); + } + + private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme) + { + var switchFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding(20), + ChildrenEnumerable = ruleset.CreateAllMods() + .GroupBy(mod => mod.Type) + .Select(group => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5), + ChildrenEnumerable = group.Select(mod => new ModSwitchTiny(mod)) + }) + }; + + if (colourScheme != null) + { + return new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value)) + }, + Child = switchFlow + }; + } + + return switchFlow; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs new file mode 100644 index 0000000000..093c2271b6 --- /dev/null +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +#nullable enable + +namespace osu.Game.Rulesets.UI +{ + public class ModSwitchTiny : CompositeDrawable + { + public BindableBool Active { get; } = new BindableBool(); + + private readonly IMod mod; + + private readonly Box background; + private readonly OsuSpriteText acronymText; + + private Color4 activeForegroundColour; + private Color4 inactiveForegroundColour; + + private Color4 activeBackgroundColour; + private Color4 inactiveBackgroundColour; + + public ModSwitchTiny(IMod mod) + { + this.mod = mod; + Size = new Vector2(73, 30); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + acronymText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, + Font = OsuFont.Numeric.With(size: 24, weight: FontWeight.Black), + Text = mod.Acronym, + Margin = new MarginPadding + { + Top = 4 + } + } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider? colourProvider) + { + inactiveBackgroundColour = colourProvider?.Background5 ?? colours.Gray3; + activeBackgroundColour = colours.ForModType(mod.Type); + + inactiveForegroundColour = colourProvider?.Background2 ?? colours.Gray5; + activeForegroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Active.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + acronymText.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint); + background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint); + } + } +} From cfc41a0a366cc0c311cbee0cf1aa9ea37d87784a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:03:12 +0100 Subject: [PATCH 05/17] Implement small mod switch --- .../UserInterface/TestSceneModSwitchSmall.cs | 85 ++++++++++++++ osu.Game/Rulesets/UI/ModSwitchSmall.cs | 109 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs create mode 100644 osu.Game/Rulesets/UI/ModSwitchSmall.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs new file mode 100644 index 0000000000..447352b7a6 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSwitchSmall : OsuTestScene + { + [Test] + public void TestOsu() => createSwitchTestFor(new OsuRuleset()); + + [Test] + public void TestTaiko() => createSwitchTestFor(new TaikoRuleset()); + + [Test] + public void TestCatch() => createSwitchTestFor(new CatchRuleset()); + + [Test] + public void TestMania() => createSwitchTestFor(new ManiaRuleset()); + + private void createSwitchTestFor(Ruleset ruleset) + { + AddStep("no colour scheme", () => Child = createContent(ruleset, null)); + + foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast()) + { + AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme)); + } + + AddToggleStep("toggle active", active => this.ChildrenOfType().ForEach(s => s.Active.Value = active)); + } + + private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme) + { + var switchFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding(20), + ChildrenEnumerable = ruleset.CreateAllMods() + .GroupBy(mod => mod.Type) + .Select(group => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5), + ChildrenEnumerable = group.Select(mod => new ModSwitchSmall(mod)) + }) + }; + + if (colourScheme != null) + { + return new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value)) + }, + Child = switchFlow + }; + } + + return switchFlow; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs new file mode 100644 index 0000000000..5d9d9075bc --- /dev/null +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +#nullable enable + +namespace osu.Game.Rulesets.UI +{ + public class ModSwitchSmall : CompositeDrawable + { + public BindableBool Active { get; } = new BindableBool(); + + private readonly IMod mod; + + private const float size = 60; + + private readonly SpriteIcon background; + private readonly SpriteIcon? modIcon; + + private Color4 activeForegroundColour; + private Color4 inactiveForegroundColour; + + private Color4 activeBackgroundColour; + private Color4 inactiveBackgroundColour; + + public ModSwitchSmall(IMod mod) + { + this.mod = mod; + + AutoSizeAxes = Axes.Both; + + FillFlowContainer contentFlow; + ModSwitchTiny tinySwitch; + + InternalChildren = new Drawable[] + { + background = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(size), + Icon = OsuIcon.ModBg + }, + contentFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 4), + Direction = FillDirection.Vertical, + Child = tinySwitch = new ModSwitchTiny(mod) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scale = new Vector2(0.6f), + Active = { BindTarget = Active } + } + } + }; + + if (mod.Icon != null) + { + contentFlow.Insert(-1, modIcon = new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(21), + Icon = mod.Icon.Value + }); + tinySwitch.Scale = new Vector2(0.3f); + } + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider? colourProvider) + { + inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3; + activeForegroundColour = colours.ForModType(mod.Type); + + inactiveBackgroundColour = colourProvider?.Background2 ?? colours.Gray5; + activeBackgroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Active.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + modIcon?.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint); + background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint); + } + } +} From d6032a2335e0de00dae43ce7f6fcef95ffbbe50e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:55:52 +0900 Subject: [PATCH 06/17] Fix beatmap overlay not re-fetching results after login --- .../BeatmapListing/BeatmapListingFilterControl.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 157753c09f..0f87f04270 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -67,6 +67,8 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private IAPIProvider api { get; set; } + private IBindable apiUser; + public BeatmapListingFilterControl() { RelativeSizeAxes = Axes.X; @@ -127,7 +129,7 @@ namespace osu.Game.Overlays.BeatmapListing } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, IAPIProvider api) { sortControlBackground.Colour = colourProvider.Background4; } @@ -161,6 +163,9 @@ namespace osu.Game.Overlays.BeatmapListing sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); + + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(_ => queueUpdateSearch()); } public void TakeFocus() => searchControl.TakeFocus(); @@ -190,6 +195,9 @@ namespace osu.Game.Overlays.BeatmapListing resetSearch(); + if (!api.IsLoggedIn) + return; + queryChangedDebounce = Scheduler.AddDelayed(() => { resetSearch(); From e744840d07aec8f0f6a75abb81ba1bc9dd9dbba8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:56:04 +0900 Subject: [PATCH 07/17] Ensure old results are cleared from beatmap overlay on logout --- osu.Game/Overlays/BeatmapListingOverlay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index fbed234cc7..3476968ded 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Localisation; using osu.Framework.Graphics; @@ -19,6 +20,7 @@ using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing; using osu.Game.Resources.Localisation.Web; @@ -32,6 +34,11 @@ namespace osu.Game.Overlays [Resolved] private PreviewTrackManager previewTrackManager { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + + private IBindable apiUser; + private Drawable currentContent; private Container panelTarget; private FillFlowContainer foundContent; @@ -93,6 +100,13 @@ namespace osu.Game.Overlays { base.LoadComplete(); filterControl.CardSize.BindValueChanged(_ => onCardSizeChanged()); + + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(_ => + { + if (api.IsLoggedIn) + addContentToResultsArea(Drawable.Empty()); + }); } public void ShowWithSearch(string query) From 347a2346b9d73352a482b882d81ad49cf8ed4176 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:40:30 +0900 Subject: [PATCH 08/17] Fix `TestSceneEditorSaving` not waiting for timeline load As seen at https://github.com/ppy/osu/runs/5276431764?check_suite_focus=true. --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index adaa24d542..d1c1558003 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -49,6 +49,8 @@ namespace osu.Game.Tests.Visual.Editing double originalTimelineZoom = 0; double changedTimelineZoom = 0; + AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16); AddStep("Set timeline zoom", () => { From ca0a04115374368184ca8363ac7026384b9c9dcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 16:45:04 +0900 Subject: [PATCH 09/17] Fix missing escaping causing test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4c477d58b6..39a51876c2 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -269,7 +269,7 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); From ed008267d7a932c64dac5a3566af1bcdd145e415 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 16:45:18 +0900 Subject: [PATCH 10/17] Fix one more case of escaping not being present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 39a51876c2..333c554d44 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -272,7 +272,7 @@ namespace osu.Game.Online.Chat handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels - handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); + handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel); string empty = ""; while (space-- > 0) From 5efffa208acc118d8acf16cae9a609318e85b7b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:08:09 +0900 Subject: [PATCH 11/17] Add test coverage of beatmap set overlay actually showing requested beatmap --- .../TestSceneStartupBeatmapDisplay.cs | 34 ++++++++++++++++++- .../TestSceneStartupBeatmapSetDisplay.cs | 32 ++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs index 1efa24435e..961b7dedc3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs @@ -5,18 +5,50 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; namespace osu.Game.Tests.Visual.Navigation { public class TestSceneStartupBeatmapDisplay : OsuGameTestScene { - protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://b/75" }); + private const int requested_beatmap_id = 75; + private const int requested_beatmap_set_id = 1; + + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://b/{requested_beatmap_id}" }); + + [SetUp] + public void Setup() => Schedule(() => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = requested_beatmap_id, + }).ToArray(); + + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); [Test] public void TestBeatmapLink() { AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Beatmap.Value.OnlineID == requested_beatmap_id); } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs index 1339c514e4..1aa56896d3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs @@ -5,18 +5,48 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Navigation { public class TestSceneStartupBeatmapSetDisplay : OsuGameTestScene { - protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://s/1" }); + private const int requested_beatmap_set_id = 1; + + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://s/{requested_beatmap_set_id}" }); + + [SetUp] + public void Setup() => Schedule(() => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = 75, + }).ToArray(); + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); [Test] public void TestBeatmapSetLink() { AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id); } } } From 6de4e05e492aa7ff03e068000ffdf83cb2dd65d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:14:00 +0900 Subject: [PATCH 12/17] Fix current selection not being correctly maintained when `BeatmapPicker` updates its display --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 59e8e8db3c..031442814d 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -183,7 +183,14 @@ namespace osu.Game.Overlays.BeatmapSet } starRatingContainer.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + + // If a selection is already made, try and maintain it. + if (Beatmap.Value != null) + Beatmap.Value = Difficulties.FirstOrDefault(b => b.Beatmap.OnlineID == Beatmap.Value.OnlineID)?.Beatmap; + + // Else just choose the first available difficulty for now. + Beatmap.Value ??= Difficulties.FirstOrDefault()?.Beatmap; + plays.Value = BeatmapSet?.PlayCount ?? 0; favourites.Value = BeatmapSet?.FavouriteCount ?? 0; From d1d6847d3205d5ea1226dc063d7315b95cd47491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 22:24:37 +0100 Subject: [PATCH 13/17] Add comment about split usage in osu:// protocol link handling --- osu.Game/OsuGame.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 390e96d768..77eec004c0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -921,6 +921,9 @@ namespace osu.Game if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) throw new ArgumentException("Invalid osu URL provided.", nameof(url)); + // note that `StringSplitOptions.RemoveEmptyEntries` is not explicitly specified here + // in order to ensure that the protocol URL is valid (i.e. it has two slashes in the `osu://` part, + // causing the link content to be stored to the 2nd index rather than the 1st). string[] pieces = url.Split('/'); switch (pieces[2]) From d8fa443ea0eb21f607253aa9d8de679d94a51800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 23:22:00 +0100 Subject: [PATCH 14/17] Extract default mod switch measurements to constants For use later when specific sizes/scales of the mod switches are desired. --- osu.Game/Rulesets/UI/ModSwitchSmall.cs | 6 +++--- osu.Game/Rulesets/UI/ModSwitchTiny.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs index 5d9d9075bc..676bbac95c 100644 --- a/osu.Game/Rulesets/UI/ModSwitchSmall.cs +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.UI { public BindableBool Active { get; } = new BindableBool(); - private readonly IMod mod; + public const float DEFAULT_SIZE = 60; - private const float size = 60; + private readonly IMod mod; private readonly SpriteIcon background; private readonly SpriteIcon? modIcon; @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.UI { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(size), + Size = new Vector2(DEFAULT_SIZE), Icon = OsuIcon.ModBg }, contentFlow = new FillFlowContainer diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs index 093c2271b6..b1d453f588 100644 --- a/osu.Game/Rulesets/UI/ModSwitchTiny.cs +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.UI { public BindableBool Active { get; } = new BindableBool(); + public const float DEFAULT_HEIGHT = 30; + private readonly IMod mod; private readonly Box background; @@ -36,7 +38,7 @@ namespace osu.Game.Rulesets.UI public ModSwitchTiny(IMod mod) { this.mod = mod; - Size = new Vector2(73, 30); + Size = new Vector2(73, DEFAULT_HEIGHT); InternalChild = new CircularContainer { From 7bdcb5952ed67bc895da12833767bc3746326d85 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 23 Feb 2022 00:36:56 +0100 Subject: [PATCH 15/17] Fix handling badly-formatted osu:// urls --- osu.Game/Online/Chat/MessageFormatter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d7974004b1..3517777325 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -236,8 +236,7 @@ namespace osu.Game.Online.Chat break; default: - linkType = LinkAction.External; - break; + return new LinkDetails(LinkAction.External, url); } return new LinkDetails(linkType, args[2]); From 87da650dfb5651c608562865a37db35a0e04c32e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 13:51:25 +0900 Subject: [PATCH 16/17] Update framework --- osu.Android.props | 2 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 24a0d20874..526ce959a6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index cd675e467b..1107089a46 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); + var dllStore = new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); defaultSkin = new DefaultLegacySkin(this); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4c6f81defa..7dfd099df1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 99b9de3fe2..9d0e1790f0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 5d73691de4e13ed827d0a731ea909e7b33d115d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:02:39 +0900 Subject: [PATCH 17/17] Use existing `HandleLink` flow rather than reimplmenting --- osu.Game/OsuGame.cs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 77eec004c0..fa5a336b7c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -906,7 +906,7 @@ namespace osu.Game if (firstPath.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) { - handleOsuProtocolUrl(firstPath); + HandleLink(firstPath); } else { @@ -916,30 +916,6 @@ namespace osu.Game } } - private void handleOsuProtocolUrl(string url) - { - if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) - throw new ArgumentException("Invalid osu URL provided.", nameof(url)); - - // note that `StringSplitOptions.RemoveEmptyEntries` is not explicitly specified here - // in order to ensure that the protocol URL is valid (i.e. it has two slashes in the `osu://` part, - // causing the link content to be stored to the 2nd index rather than the 1st). - string[] pieces = url.Split('/'); - - switch (pieces[2]) - { - case "s": - if (int.TryParse(pieces[3], out int beatmapSetId)) - ShowBeatmapSet(beatmapSetId); - break; - - case "b": - if (int.TryParse(pieces[3], out int beatmapId)) - ShowBeatmap(beatmapId); - break; - } - } - private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) { otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide());