From 64baa4d01ae7bb9a2659a83c3e3238d2fbb07ef2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Sep 2023 18:20:07 +0900 Subject: [PATCH 01/12] Add test coverage of failing slider tick generation edge case --- .../OsuBeatmapConversionTest.cs | 1 + ...r-ticks-edge-case-expected-conversion.json | 39 +++++++++++++++++++ .../Beatmaps/slider-ticks-edge-case.osu | 31 +++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index b94e9f38c6..08eea35425 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase("basic")] [TestCase("colinear-perfect-curve")] [TestCase("slider-ticks")] + [TestCase("slider-ticks-edge-case")] [TestCase("repeat-slider")] [TestCase("uneven-repeat-slider")] [TestCase("old-stacking")] diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json new file mode 100644 index 0000000000..657121d812 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json @@ -0,0 +1,39 @@ +{ + "Mappings": [ + { + "StartTime": 7493.0, + "Objects": [ + { + "StartTime": 7493.0, + "EndTime": 7493.0, + "X": 130.0, + "Y": 232.0, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 7843.0, + "EndTime": 7843.0, + "X": 33.7820168, + "Y": 208.9957, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 7817.0, + "EndTime": 7817.0, + "X": 30.9946651, + "Y": 208.5157, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + } + ] + } + ] +} diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu new file mode 100644 index 0000000000..daf35e1d2b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu @@ -0,0 +1,31 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 + +[Difficulty] +HPDrainRate:3 +CircleSize:3.4 +OverallDifficulty:4 +ApproachRate:5.5 +SliderMultiplier:1.1 +SliderTickRate:1 + +[Events] +//Background and Video events +0,0,"aa.jpg",0,0 +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +6967,350.877192982456,6,2,1,55,1,0 +6967,-100,3,2,1,55,0,0 +7493,-111.111111111111,3,2,1,55,0,0 + +[HitObjects] +130,232,7493,6,0,P|78:218|28:208,1,101.82149697876,0|0,3:2|2:0,2:0:0:0: From 22ee64cfdad8af87baf37cf9a925b59f9ca6837c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Sep 2023 18:19:54 +0900 Subject: [PATCH 02/12] Fix sliders not always being the correct length This is another similar case where stable floating point precision comes into play due to use of `hitObjectManager.Beatmap.BpmMultiplierAt` (see https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/GameplayElements/HitObjects/Osu/SliderOsu.cs#L680) Closes #24708. --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 8bf19f030f..f40c301220 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocityMultiplier; + double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * ((IHasSliderVelocity)this).GetPrecisionAdjustedSliderVelocityMultiplier(OsuRuleset.SHORT_NAME); Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; From 66751ef5bbd21e5426815795917216506cf36069 Mon Sep 17 00:00:00 2001 From: Lukynka CZE Date: Thu, 14 Sep 2023 17:46:29 +0200 Subject: [PATCH 03/12] add regex --- 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 10a005d71a..d04d971213 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.Chat // http[s]://.[:port][/path][?query][#fragment] private static readonly Regex advanced_link_regex = new Regex( // protocol - @"(?[a-z]*?:\/\/" + + @"(?(https?|osu)[a-z]*?://" + // domain + tld @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + // port (optional) From 25926af78258e25a32d2bec79d03c8a0fddd17e7 Mon Sep 17 00:00:00 2001 From: Lukynka CZE Date: Thu, 14 Sep 2023 17:46:42 +0200 Subject: [PATCH 04/12] add unit test --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 47f4410b07..f0dcd527a4 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -32,8 +32,17 @@ namespace osu.Game.Tests.Chat Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a gopher://really-old-protocol we don't support." }); Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("gopher://really-old-protocol", result.Links[0].Url); + Assert.AreEqual(0, result.Links.Count); + } + + [Test] + public void TestSupportedProtocolLinkParsing() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "forgotspacehttps://dev.ppy.sh joinmyosump://12345 jointheosu://chan/#english" }); + + Assert.AreEqual("https://dev.ppy.sh", result.Links[0].Url); + Assert.AreEqual("osump://12345", result.Links[1].Url); + Assert.AreEqual("osu://chan/#english", result.Links[2].Url); } [Test] From 2a18f76b026441189224c5c61a9737ff06c7aea1 Mon Sep 17 00:00:00 2001 From: Lukynka CZE Date: Thu, 14 Sep 2023 17:48:10 +0200 Subject: [PATCH 05/12] add visual test --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index ecf9b68297..7616b9b83c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -64,6 +64,9 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("test!"); addMessageWithChecks("dev.ppy.sh!"); addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External); + addMessageWithChecks("http://dev.ppy.sh!", 1, expectedActions: LinkAction.External); + addMessageWithChecks("forgothttps://dev.ppy.sh!", 1, expectedActions: LinkAction.External); + addMessageWithChecks("forgothttp://dev.ppy.sh!", 1, expectedActions: LinkAction.External); addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.OpenWiki); addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); @@ -84,9 +87,11 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("feels important", 0, true, true); 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 gameosump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); 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{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); addMessageWithChecks("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20"); From 9b8fdcbcdc7fef881d9804cd4afc93c4a448536b Mon Sep 17 00:00:00 2001 From: Lukynka CZE Date: Thu, 14 Sep 2023 17:55:53 +0200 Subject: [PATCH 06/12] somehow accidentally removed backslashes --- 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 d04d971213..db4d0b1363 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.Chat // http[s]://.[:port][/path][?query][#fragment] private static readonly Regex advanced_link_regex = new Regex( // protocol - @"(?(https?|osu)[a-z]*?://" + + @"(?(https?|osu)[a-z]*?:\/\/" + // domain + tld @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + // port (optional) From 86c46a5b3f6bebc56b01ce585bf3c01795c68449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Sep 2023 12:07:59 +0200 Subject: [PATCH 07/12] Manually reorder objects in expected mapping The test added in 64baa4d01ae7bb9a2659a83c3e3238d2fbb07ef2 was previously failing despite applying a fix. This was caused by the fact that in stable, the last `sliderScoreTimingPoint` (i.e. the `LegacyLastTick` is pulled back by 36ms, but the list of all of them is not re-sorted afterwards, causing objects to be exported in non-chronological order to the resultant conversion mapping. lazer correctly sorts the objects, causing a false positive. --- ...ider-ticks-edge-case-expected-conversion.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json index 657121d812..6d97b643b1 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json @@ -14,20 +14,20 @@ } }, { - "StartTime": 7843.0, - "EndTime": 7843.0, - "X": 33.7820168, - "Y": 208.9957, + "StartTime": 7817.0, + "EndTime": 7817.0, + "X": 30.9946651, + "Y": 208.5157, "StackOffset": { "X": 0.0, "Y": 0.0 } }, { - "StartTime": 7817.0, - "EndTime": 7817.0, - "X": 30.9946651, - "Y": 208.5157, + "StartTime": 7843.0, + "EndTime": 7843.0, + "X": 33.7820168, + "Y": 208.9957, "StackOffset": { "X": 0.0, "Y": 0.0 From 5c6cd879ddae01fd86b29622d51652847b99a5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Sep 2023 12:21:58 +0200 Subject: [PATCH 08/12] Adjust slider calculations to new method API Code originally read Velocity = scoringDistance / beatLength = BASE_SCORING_DISTANCE * SliderMultiplier * GetPrecisionAdjustedSliderVelocityMultiplier() / beatLength Given (mathematically, floats are not generally as forgiving): GetPrecisionAdjustedBeatLength() = beatLength / GetPrecisionAdjustedSliderVelocityMultiplier() it follows that (inverting both sides): 1 / GetPrecisionAdjustedBeatLength() = GetPrecisionAdjustedSliderVelocityMultiplier() / beatLength and therefore Velocity = BASE_SCORING_DISTANCE * SliderMultiplier * GetPrecisionAdjustedSliderVelocityMultiplier() / beatLength = BASE_SCORING_DISTANCE * SliderMultiplier / GetPrecisionAdjustedBeatLength() and to recover `scoringDistance` scoringDistance = Velocity * beatLength --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index f40c301220..e05dbd8ea6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -16,6 +16,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; @@ -166,9 +167,11 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * ((IHasSliderVelocity)this).GetPrecisionAdjustedSliderVelocityMultiplier(OsuRuleset.SHORT_NAME); + Velocity = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(this, timingPoint, OsuRuleset.SHORT_NAME); + // WARNING: this is intentionally not computed as `BASE_SCORING_DISTANCE * difficulty.SliderMultiplier` + // for backwards compatibility reasons (intentionally introducing floating point errors to match stable). + double scoringDistance = Velocity * timingPoint.BeatLength; - Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } From c2685da94c8c1ff329c6fb966de5035d6eddb98d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 13 Sep 2023 23:38:54 -0700 Subject: [PATCH 09/12] Fix dummy beatmap showing AR 5 on song select --- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 8089d789c1..e2e541811d 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -36,9 +36,10 @@ namespace osu.Game.Beatmaps BeatmapSet = new BeatmapSetInfo(), Difficulty = new BeatmapDifficulty { - DrainRate = 0, CircleSize = 0, + DrainRate = 0, OverallDifficulty = 0, + ApproachRate = 0, }, Ruleset = new DummyRuleset().RulesetInfo }, audio) From a2df123c6d7271bfdbe867247f3d7064d6973d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Sep 2023 09:19:12 +0200 Subject: [PATCH 10/12] Add failing test case for fake (but still incorrectly allowed) protocol --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index f0dcd527a4..1baa737a9c 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -35,6 +35,15 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(0, result.Links.Count); } + [Test] + public void TestFakeProtocolLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a osunotarealprotocol://completely-made-up-protocol we don't support." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(0, result.Links.Count); + } + [Test] public void TestSupportedProtocolLinkParsing() { From d3cc6dbaa09a1ad8396db068c3635bc18b7723d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Sep 2023 09:20:14 +0200 Subject: [PATCH 11/12] Fix link protocol allowlist allowing too much --- 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 db4d0b1363..667175117f 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.Chat // http[s]://.[:port][/path][?query][#fragment] private static readonly Regex advanced_link_regex = new Regex( // protocol - @"(?(https?|osu)[a-z]*?:\/\/" + + @"(?(https?|osu(mp)?):\/\/" + // domain + tld @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + // port (optional) From 5c2413c06b5a79658cd245871654c7814c558e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Sep 2023 11:30:14 +0200 Subject: [PATCH 12/12] Implement nano beatmap card --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 6 + .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 3 + .../Drawables/Cards/BeatmapCardNano.cs | 166 ++++++++++++++++++ .../Drawables/Cards/BeatmapCardSize.cs | 1 + .../BeatmapListingCardSizeTabControl.cs | 4 + 5 files changed, 180 insertions(+) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index d4018be7fc..fed26d8acb 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -260,6 +260,12 @@ namespace osu.Game.Tests.Visual.Beatmaps AddStep($"set {scheme} scheme", () => Child = createContent(scheme, creationFunc)); } + [Test] + public void TestNano() + { + createTestCase(beatmapSetInfo => new BeatmapCardNano(beatmapSetInfo)); + } + [Test] public void TestNormal() { diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 94b2956b4e..a16f6d5689 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -89,6 +89,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards { switch (size) { + case BeatmapCardSize.Nano: + return new BeatmapCardNano(beatmapSet); + case BeatmapCardSize.Normal: return new BeatmapCardNormal(beatmapSet, allowExpansion); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs new file mode 100644 index 0000000000..ba2142d28f --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs @@ -0,0 +1,166 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public partial class BeatmapCardNano : BeatmapCard + { + protected override Drawable IdleContent => idleBottomContent; + protected override Drawable DownloadInProgressContent => downloadProgressBar; + + private const float height = 60; + private const float width = 300; + private const float cover_width = 80; + + [Cached] + private readonly BeatmapCardContent content; + + private BeatmapCardThumbnail thumbnail = null!; + private CollapsibleButtonContainer buttonContainer = null!; + + private FillFlowContainer idleBottomContent = null!; + private BeatmapCardDownloadProgressBar downloadProgressBar = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public BeatmapCardNano(APIBeatmapSet beatmapSet) + : base(beatmapSet, false) + { + content = new BeatmapCardContent(height); + } + + [BackgroundDependencyLoader] + private void load() + { + Width = width; + Height = height; + + Child = content.With(c => + { + c.MainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + thumbnail = new BeatmapCardThumbnail(BeatmapSet) + { + Name = @"Left (icon) area", + Size = new Vector2(cover_width, height), + Padding = new MarginPadding { Right = CORNER_RADIUS }, + }, + buttonContainer = new CollapsibleButtonContainer(BeatmapSet) + { + X = cover_width - CORNER_RADIUS, + Width = width - cover_width + CORNER_RADIUS, + FavouriteState = { BindTarget = FavouriteState }, + ButtonsCollapsedWidth = CORNER_RADIUS, + ButtonsExpandedWidth = 30, + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new TruncatingSpriteText + { + Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), + Font = OsuFont.Default.With(size: 19, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + }, + new TruncatingSpriteText + { + Text = createArtistText(), + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + }, + } + }, + new Container + { + Name = @"Bottom content", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Children = new Drawable[] + { + idleBottomContent = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + AlwaysPresent = true, + Children = new Drawable[] + { + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.Margin = new MarginPadding { Top = 2 }; + d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); + d.AddUserLink(BeatmapSet.Author); + }), + } + }, + downloadProgressBar = new BeatmapCardDownloadProgressBar + { + RelativeSizeAxes = Axes.X, + Height = 6, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { BindTarget = DownloadTracker.State }, + Progress = { BindTarget = DownloadTracker.Progress } + } + } + } + } + } + } + }; + c.ExpandedContent = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, + Child = new BeatmapCardDifficultyList(BeatmapSet) + }; + c.Expanded.BindTarget = Expanded; + }); + } + + private LocalisableString createArtistText() + { + var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist); + return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); + } + + protected override void UpdateState() + { + base.UpdateState(); + + bool showDetails = IsHovered; + + buttonContainer.ShowDetails.Value = showDetails; + thumbnail.Dimmed.Value = showDetails; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardSize.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardSize.cs index 098265506d..0b5acc4a05 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardSize.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardSize.cs @@ -8,6 +8,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards /// public enum BeatmapCardSize { + Nano, Normal, Extra } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs index feb0c27ee7..9cd0031e3d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs @@ -22,8 +22,12 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapListingCardSizeTabControl() { AutoSizeAxes = Axes.Both; + + Items = new[] { BeatmapCardSize.Normal, BeatmapCardSize.Extra }; } + protected override bool AddEnumEntriesAutomatically => false; + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { AutoSizeAxes = Axes.Both,