From fa5922337da11583469482d26b8b4043badc8574 Mon Sep 17 00:00:00 2001 From: Plextora <71889427+Plextora@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:17:03 -0500 Subject: [PATCH 01/92] Fail on slider tail miss option in Sudden Death --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index e661610fe7..f781bf0b90 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -3,7 +3,12 @@ using System; using System.Linq; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Mods { @@ -13,5 +18,16 @@ namespace osu.Game.Rulesets.Osu.Mods { typeof(OsuModTargetPractice), }).ToArray(); + + [SettingSource("Fail on slider tail miss", "Fail when missing on the end of a slider")] + public BindableBool SliderTailMiss { get; } = new BindableBool(); + + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + { + if (SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) + return true; + + return result.Type.AffectsCombo() && !result.IsHit; + } } } From 87697a72e333d1468a35d4a3fec388319cc16e2a Mon Sep 17 00:00:00 2001 From: Plextora <71889427+Plextora@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:32:09 -0500 Subject: [PATCH 02/92] Rename to PFC mode --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index f781bf0b90..3a65ba3b10 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(OsuModTargetPractice), }).ToArray(); - [SettingSource("Fail on slider tail miss", "Fail when missing on the end of a slider")] + [SettingSource("PFC mode", "Fail when missing on a slider tail")] public BindableBool SliderTailMiss { get; } = new BindableBool(); protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) From 420c5577d3a8aef97af82158110211c72cf5f8aa Mon Sep 17 00:00:00 2001 From: Plextora <71889427+Plextora@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:55:30 -0500 Subject: [PATCH 03/92] Rename option (again) --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index 3a65ba3b10..fb587a94ca 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(OsuModTargetPractice), }).ToArray(); - [SettingSource("PFC mode", "Fail when missing on a slider tail")] + [SettingSource("Fail when missing on a slider tail")] public BindableBool SliderTailMiss { get; } = new BindableBool(); protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) From 5c9278ee2f5c044e0b6565973497b559f620ab5e Mon Sep 17 00:00:00 2001 From: Plextora <71889427+Plextora@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:56:42 -0500 Subject: [PATCH 04/92] One line return for FailCondition --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index fb587a94ca..a90d44c473 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -22,12 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Fail when missing on a slider tail")] public BindableBool SliderTailMiss { get; } = new BindableBool(); - protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - { - if (SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) - return true; - - return result.Type.AffectsCombo() && !result.IsHit; - } + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => ( + SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) || (result.Type.AffectsCombo() && !result.IsHit); } } From 047c448741a6c2ab038a04085ebab97048e8473d Mon Sep 17 00:00:00 2001 From: Plextora <71889427+Plextora@users.noreply.github.com> Date: Sun, 22 Dec 2024 12:09:27 -0500 Subject: [PATCH 05/92] Return base for default FailCondition --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index a90d44c473..ea32b4868a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Fail when missing on a slider tail")] public BindableBool SliderTailMiss { get; } = new BindableBool(); - protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => ( - SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) || (result.Type.AffectsCombo() && !result.IsHit); + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => + (SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) || base.FailCondition(healthProcessor, result); } } From fd1cc34e3fd01747eb132c04b831a22429be7c99 Mon Sep 17 00:00:00 2001 From: Plextora <71889427+Plextora@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:46:01 -0500 Subject: [PATCH 06/92] No more one line return for FailCondition --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index ea32b4868a..73d0403e3f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -22,7 +22,12 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Fail when missing on a slider tail")] public BindableBool SliderTailMiss { get; } = new BindableBool(); - protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => - (SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) || base.FailCondition(healthProcessor, result); + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + { + if (SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) + return true; + + return base.FailCondition(healthProcessor, result); + } } } From 2d75030e36c2304d86a8f617d320cc468c31a73d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:17:25 -0500 Subject: [PATCH 07/92] Change default carousel item header to 50px --- osu.Game/Screens/SelectV2/CarouselItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/CarouselItem.cs b/osu.Game/Screens/SelectV2/CarouselItem.cs index 32be33e99a..65b62be6ba 100644 --- a/osu.Game/Screens/SelectV2/CarouselItem.cs +++ b/osu.Game/Screens/SelectV2/CarouselItem.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.SelectV2 /// public sealed class CarouselItem : IComparable { - public const float DEFAULT_HEIGHT = 40; + public const float DEFAULT_HEIGHT = 50; /// /// The model this item is representing. From f2d259cd95f405cdf835fd228c18b4eebc11fbf3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:17:49 -0500 Subject: [PATCH 08/92] Cache overlay colour provider to carousel tests --- osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs index 72c9611fdb..3a83ff68c6 100644 --- a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs +++ b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs @@ -37,6 +37,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Cached(typeof(BeatmapStore))] private BeatmapStore store; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + private OsuTextFlowContainer stats = null!; private int beatmapCount; From a5fa04e4d6b8cd4852c5c172488d571ebc121809 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:18:55 -0500 Subject: [PATCH 09/92] Extend beatmap carousel width in tests --- osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs index 3a83ff68c6..a3f6eaf152 100644 --- a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs +++ b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; using osu.Game.Screens.SelectV2; @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 500, + Width = 800, RelativeSizeAxes = Axes.Y, }, }, From 206b5c93c0a8eb43c89d8fb8bc909f2e3aea9ab7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:15:53 -0500 Subject: [PATCH 10/92] Implement beatmap set header design --- .../TestSceneBeatmapCarouselSetPanel.cs | 90 ++++++ .../TestSceneUpdateBeatmapSetButtonV2.cs | 62 ++++ .../Drawables/DifficultySpectrumDisplay.cs | 69 ++-- osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 297 +++++++++++++++--- .../SelectV2/BeatmapSetPanelBackground.cs | 108 +++++++ osu.Game/Screens/SelectV2/TopLocalRankV2.cs | 108 +++++++ .../SelectV2/UpdateBeatmapSetButtonV2.cs | 198 ++++++++++++ 7 files changed, 860 insertions(+), 72 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs create mode 100644 osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs create mode 100644 osu.Game/Screens/SelectV2/TopLocalRankV2.cs create mode 100644 osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs new file mode 100644 index 0000000000..6b981d7b33 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs @@ -0,0 +1,90 @@ +// 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.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Screens.SelectV2; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneBeatmapCarouselSetPanel : ThemeComparisonTestScene + { + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + private BeatmapSetInfo beatmapSet = null!; + + public TestSceneBeatmapCarouselSetPanel() + : base(false) + { + } + + [Test] + public void TestDisplay() + { + AddStep("set beatmap", () => + { + beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) + ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) + ?? TestResources.CreateTestBeatmapSetInfo(); + CreateThemedContent(OverlayColourScheme.Aquamarine); + }); + } + + [Test] + public void TestRandomBeatmap() + { + AddStep("random beatmap", () => + { + beatmapSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).First(); + CreateThemedContent(OverlayColourScheme.Aquamarine); + }); + } + + protected override Drawable CreateContent() + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + new BeatmapSetPanel + { + Item = new CarouselItem(beatmapSet) + }, + new BeatmapSetPanel + { + Item = new CarouselItem(beatmapSet), + KeyboardSelected = { Value = true } + }, + new BeatmapSetPanel + { + Item = new CarouselItem(beatmapSet), + Expanded = { Value = true } + }, + new BeatmapSetPanel + { + Item = new CarouselItem(beatmapSet), + KeyboardSelected = { Value = true }, + Expanded = { Value = true } + }, + } + }; + } + } +} diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs new file mode 100644 index 0000000000..6e5d731453 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Screens.SelectV2; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneUpdateBeatmapSetButtonV2 : OsuTestScene + { + private UpdateBeatmapSetButtonV2 button = null!; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = button = new UpdateBeatmapSetButtonV2 + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + [Test] + public void TestNullBeatmap() + { + AddStep("null beatmap", () => button.BeatmapSet = null); + AddAssert("button invisible", () => button.Alpha == 0f); + } + + [Test] + public void TestUpdatedBeatmap() + { + AddStep("updated beatmap", () => button.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = { new BeatmapInfo() } + }); + AddAssert("button invisible", () => button.Alpha == 0f); + } + + [Test] + public void TestNonUpdatedBeatmap() + { + AddStep("non-updated beatmap", () => button.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = + { + new BeatmapInfo + { + MD5Hash = "test", + OnlineMD5Hash = "online", + LastOnlineUpdate = DateTimeOffset.Now, + } + } + }); + + AddAssert("button visible", () => button.Alpha == 1f); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs b/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs index 2fb3a8eee4..56f6c77ba8 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps.Drawables dotSize = value; if (IsLoaded) - updateDotDimensions(); + updateDisplay(); } } @@ -42,13 +42,27 @@ namespace osu.Game.Beatmaps.Drawables dotSpacing = value; if (IsLoaded) - updateDotDimensions(); + updateDisplay(); + } + } + + private IBeatmapSetInfo? beatmapSet; + + public IBeatmapSetInfo? BeatmapSet + { + get => beatmapSet; + set + { + beatmapSet = value; + + if (IsLoaded) + updateDisplay(); } } private readonly FillFlowContainer flow; - public DifficultySpectrumDisplay(IBeatmapSetInfo beatmapSet) + public DifficultySpectrumDisplay(IBeatmapSetInfo? beatmapSet = null) { AutoSizeAxes = Axes.Both; @@ -59,25 +73,31 @@ namespace osu.Game.Beatmaps.Drawables Direction = FillDirection.Horizontal, }; - // matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127 - bool collapsed = beatmapSet.Beatmaps.Count() > 12; - - foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key)) - flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed)); + BeatmapSet = beatmapSet; } protected override void LoadComplete() { base.LoadComplete(); - updateDotDimensions(); + updateDisplay(); } - private void updateDotDimensions() + private void updateDisplay() { - foreach (var group in flow) + flow.Clear(); + + if (beatmapSet == null) + return; + + // matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127 + bool collapsed = beatmapSet.Beatmaps.Count() > 12; + + foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key)) { - group.DotSize = DotSize; - group.DotSpacing = DotSpacing; + flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed, dotSize) + { + Spacing = new Vector2(DotSpacing, 0f), + }); } } @@ -86,26 +106,14 @@ namespace osu.Game.Beatmaps.Drawables private readonly int rulesetId; private readonly IEnumerable beatmapInfos; private readonly bool collapsed; + private readonly Vector2 dotSize; - public RulesetDifficultyGroup(int rulesetId, IEnumerable beatmapInfos, bool collapsed) + public RulesetDifficultyGroup(int rulesetId, IEnumerable beatmapInfos, bool collapsed, Vector2 dotSize) { this.rulesetId = rulesetId; this.beatmapInfos = beatmapInfos; this.collapsed = collapsed; - } - - public Vector2 DotSize - { - set - { - foreach (var dot in Children.OfType()) - dot.Size = value; - } - } - - public float DotSpacing - { - set => Spacing = new Vector2(value, 0); + this.dotSize = dotSize; } [BackgroundDependencyLoader] @@ -125,7 +133,7 @@ namespace osu.Game.Beatmaps.Drawables if (!collapsed) { foreach (var beatmapInfo in beatmapInfos.OrderBy(bi => bi.StarRating)) - Add(new DifficultyDot(beatmapInfo.StarRating)); + Add(new DifficultyDot(beatmapInfo.StarRating, dotSize)); } else { @@ -145,9 +153,10 @@ namespace osu.Game.Beatmaps.Drawables { private readonly double starDifficulty; - public DifficultyDot(double starDifficulty) + public DifficultyDot(double starDifficulty, Vector2 dotSize) { this.starDifficulty = starDifficulty; + Size = dotSize; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index 85d5cc097d..4706ea487a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -3,15 +3,24 @@ using System; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; 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.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -19,63 +28,182 @@ namespace osu.Game.Screens.SelectV2 { public partial class BeatmapSetPanel : PoolableDrawable, ICarouselPanel { - public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 2; + public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; + + private const float arrow_container_width = 20; + private const float corner_radius = 10; + + private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. + private const float set_x_offset = 20f; // constant X offset for beatmap set panels specifically. + private const float preselected_x_offset = 25f; + private const float expanded_x_offset = 50f; + + private const float duration = 500; [Resolved] - private BeatmapCarousel carousel { get; set; } = null!; + private BeatmapCarousel? carousel { get; set; } - private OsuSpriteText text = null!; - private Box box = null!; + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) - { - var inputRectangle = DrawRectangle; + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; - // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either below/above it. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + [Resolved] + private OsuColour colours { get; set; } = null!; - return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); - } + private Container panel = null!; + private Box backgroundBorder = null!; + private BeatmapSetPanelBackground background = null!; + private Container backgroundContainer = null!; + private FillFlowContainer mainFlowContainer = null!; + private SpriteIcon chevronIcon = null!; + private Box hoverLayer = null!; + + private OsuSpriteText titleText = null!; + private OsuSpriteText artistText = null!; + private UpdateBeatmapSetButtonV2 updateButton = null!; + private BeatmapSetOnlineStatusPill statusPill = null!; + private DifficultySpectrumDisplay difficultiesDisplay = null!; [BackgroundDependencyLoader] private void load() { - Size = new Vector2(500, HEIGHT); - Masking = true; + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + RelativeSizeAxes = Axes.X; + Height = HEIGHT; - InternalChildren = new Drawable[] + InternalChild = panel = new Container { - box = new Box + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + EdgeEffect = new EdgeEffectParameters { - Colour = Color4.Yellow.Darken(5), - Alpha = 0.8f, - RelativeSizeAxes = Axes.Both, + Type = EdgeEffectType.Shadow, + Radius = 10, }, - text = new OsuSpriteText + Children = new Drawable[] { - Padding = new MarginPadding(5), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundBorder = new Box + { + RelativeSizeAxes = Axes.Y, + Alpha = 0, + EdgeSmoothness = new Vector2(2, 0), + }, + backgroundContainer = new Container + { + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.X, + MaskingSmoothness = 2, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + background = new BeatmapSetPanelBackground + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + }, + }, + } + }, + chevronIcon = new SpriteIcon + { + X = arrow_container_width / 2, + Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(12), + Colour = colourProvider.Background5, + }, + mainFlowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + artistText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5f }, + Children = new Drawable[] + { + updateButton = new UpdateBeatmapSetButtonV2 + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f, Top = -2f }, + }, + statusPill = new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Margin = new MarginPadding { Right = 5f }, + }, + difficultiesDisplay = new DifficultySpectrumDisplay + { + DotSize = new Vector2(5, 10), + DotSpacing = 2, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }, + } + } + }, + hoverLayer = new Box + { + Colour = colours.Blue.Opacity(0.1f), + Alpha = 0, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }, + new HoverSounds(), } }; + } - Expanded.BindValueChanged(value => - { - box.FadeColour(value.NewValue ? Color4.Yellow.Darken(2) : Color4.Yellow.Darken(5), 500, Easing.OutQuint); - }); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = panel.DrawRectangle; - KeyboardSelected.BindValueChanged(value => - { - if (value.NewValue) - { - BorderThickness = 5; - BorderColour = Color4.Pink; - } - else - { - BorderThickness = 0; - } - }); + // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + + return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(_ => updateExpandedDisplay(), true); + KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); } protected override void PrepareForUse() @@ -84,16 +212,101 @@ namespace osu.Game.Screens.SelectV2 Debug.Assert(Item != null); - var beatmapSetInfo = (BeatmapSetInfo)Item.Model; + var beatmapSet = (BeatmapSetInfo)Item.Model; - text.Text = $"{beatmapSetInfo.Metadata}"; + // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). + background.Beatmap = beatmaps.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID)); - this.FadeInFromZero(500, Easing.OutQuint); + titleText.Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title); + artistText.Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist); + updateButton.BeatmapSet = beatmapSet; + statusPill.Status = beatmapSet.Status; + difficultiesDisplay.BeatmapSet = beatmapSet; + + updateExpandedDisplay(); + FinishTransforms(true); + + this.FadeInFromZero(duration, Easing.OutQuint); + } + + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + + background.Beatmap = null; + updateButton.BeatmapSet = null; + difficultiesDisplay.BeatmapSet = null; + } + + private void updateExpandedDisplay() + { + if (Item == null) + return; + + updatePanelPosition(); + + backgroundBorder.RelativeSizeAxes = Expanded.Value ? Axes.Both : Axes.Y; + backgroundBorder.Width = Expanded.Value ? 1 : arrow_container_width + corner_radius; + backgroundBorder.FadeTo(Expanded.Value ? 1 : 0, duration, Easing.OutQuint); + chevronIcon.FadeTo(Expanded.Value ? 1 : 0, duration, Easing.OutQuint); + + backgroundContainer.ResizeHeightTo(Expanded.Value ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint); + backgroundContainer.MoveToX(Expanded.Value ? arrow_container_width : 0, duration, Easing.OutQuint); + mainFlowContainer.MoveToX(Expanded.Value ? arrow_container_width : 0, duration, Easing.OutQuint); + + panel.EdgeEffect = panel.EdgeEffect with { Radius = Expanded.Value ? 15 : 10 }; + + panel.FadeEdgeEffectTo(Expanded.Value + ? Color4Extensions.FromHex(@"4EBFFF").Opacity(0.5f) + : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); + } + + private void updateKeyboardSelectedDisplay() + { + updatePanelPosition(); + updateHover(); + } + + private void updatePanelPosition() + { + float x = glow_offset + set_x_offset + expanded_x_offset + preselected_x_offset; + + if (Expanded.Value) + x -= expanded_x_offset; + + if (KeyboardSelected.Value) + x -= preselected_x_offset; + + this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); + } + + private void updateHover() + { + bool hovered = IsHovered || KeyboardSelected.Value; + + if (hovered) + hoverLayer.FadeIn(100, Easing.OutQuint); + else + hoverLayer.FadeOut(1000, Easing.OutQuint); + } + + protected override bool OnHover(HoverEvent e) + { + updateHover(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateHover(); + base.OnHoverLost(e); } protected override bool OnClick(ClickEvent e) { - carousel.CurrentSelection = Item!.Model; + if (carousel != null) + carousel.CurrentSelection = Item!.Model; + return true; } diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs new file mode 100644 index 0000000000..435a0ad262 --- /dev/null +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class BeatmapSetPanelBackground : ModelBackedDrawable + { + protected override bool TransformImmediately => true; + + public WorkingBeatmap? Beatmap + { + get => Model; + set => Model = value; + } + + protected override Drawable CreateDrawable(WorkingBeatmap? model) => new BackgroundSprite(model); + + private partial class BackgroundSprite : CompositeDrawable + { + private readonly WorkingBeatmap? working; + + public BackgroundSprite(WorkingBeatmap? working) + { + this.working = working; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + var texture = working?.GetPanelBackground(); + + if (texture != null) + { + InternalChildren = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + Texture = texture, + }, + 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, + }, + } + }, + }; + } + else + { + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }; + } + } + } + } +} diff --git a/osu.Game/Screens/SelectV2/TopLocalRankV2.cs b/osu.Game/Screens/SelectV2/TopLocalRankV2.cs new file mode 100644 index 0000000000..241e92a67d --- /dev/null +++ b/osu.Game/Screens/SelectV2/TopLocalRankV2.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 System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Models; +using osu.Game.Online.API; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osuTK; +using Realms; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class TopLocalRankV2 : CompositeDrawable + { + private BeatmapInfo? beatmap; + + public BeatmapInfo? Beatmap + { + get => beatmap; + set + { + beatmap = value; + + if (IsLoaded) + updateSubscription(); + } + } + + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private RealmAccess realm { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private IDisposable? scoreSubscription; + + private readonly UpdateableRank updateable; + + public ScoreRank? DisplayedRank => updateable.Rank; + + public TopLocalRankV2(BeatmapInfo? beatmap = null) + { + AutoSizeAxes = Axes.Both; + + InternalChild = updateable = new UpdateableRank + { + Size = new Vector2(40, 20), + Alpha = 0, + }; + + Beatmap = beatmap; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ruleset.BindValueChanged(_ => updateSubscription(), true); + } + + private void updateSubscription() + { + scoreSubscription?.Dispose(); + + if (beatmap == null) + return; + + scoreSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmap.ID, ruleset.Value.ShortName), + localScoresChanged); + } + + private void localScoresChanged(IRealmCollection sender, ChangeSet? changes) + { + // This subscription may fire from changes to linked beatmaps, which we don't care about. + // It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications. + if (changes?.HasCollectionChanges() == false) + return; + + ScoreInfo? topScore = sender.MaxBy(info => (info.TotalScore, -info.Date.UtcDateTime.Ticks)); + updateable.Rank = topScore?.Rank; + updateable.Alpha = topScore != null ? 1 : 0; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + scoreSubscription?.Dispose(); + } + } +} diff --git a/osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs b/osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs new file mode 100644 index 0000000000..2d1ce4ba48 --- /dev/null +++ b/osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs @@ -0,0 +1,198 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +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.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays; +using osu.Game.Screens.Select.Carousel; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class UpdateBeatmapSetButtonV2 : OsuAnimatedButton + { + private BeatmapSetInfo? beatmapSet; + + public BeatmapSetInfo? BeatmapSet + { + get => beatmapSet; + set + { + beatmapSet = value; + + if (IsLoaded) + beatmapChanged(); + } + } + + private SpriteIcon icon = null!; + private Box progressFill = null!; + + [Resolved] + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private LoginOverlay? loginOverlay { get; set; } + + [Resolved] + private IDialogOverlay? dialogOverlay { get; set; } + + public UpdateBeatmapSetButtonV2() + { + Size = new Vector2(75f, 22f); + } + + private Bindable preferNoVideo = null!; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + const float icon_size = 14; + + preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); + + Content.Anchor = Anchor.Centre; + Content.Origin = Anchor.Centre; + Content.Shear = new Vector2(OsuGame.SHEAR, 0); + + Content.AddRange(new Drawable[] + { + progressFill = new Box + { + Colour = Color4.White, + Alpha = 0.2f, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Width = 0, + }, + new FillFlowContainer + { + Padding = new MarginPadding { Horizontal = 5, Vertical = 3 }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4), + Shear = new Vector2(-OsuGame.SHEAR, 0), + Children = new Drawable[] + { + new Container + { + Size = new Vector2(icon_size), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.SyncAlt, + Size = new Vector2(icon_size), + }, + } + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Text = "Update", + } + } + }, + }); + + Action = performUpdate; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + beatmapChanged(); + } + + private void beatmapChanged() + { + Alpha = beatmapSet?.AllBeatmapsUpToDate == false ? 1 : 0; + icon.Spin(4000, RotationDirection.Clockwise); + } + + protected override bool OnHover(HoverEvent e) + { + icon.Spin(400, RotationDirection.Clockwise); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.Spin(4000, RotationDirection.Clockwise); + base.OnHoverLost(e); + } + + private bool updateConfirmed; + + private void performUpdate() + { + Debug.Assert(beatmapSet != null); + + if (!api.IsLoggedIn) + { + loginOverlay?.Show(); + return; + } + + if (dialogOverlay != null && beatmapSet.Status == BeatmapOnlineStatus.LocallyModified && !updateConfirmed) + { + dialogOverlay.Push(new UpdateLocalConfirmationDialog(() => + { + updateConfirmed = true; + performUpdate(); + })); + + return; + } + + updateConfirmed = false; + + beatmapDownloader.DownloadAsUpdate(beatmapSet, preferNoVideo.Value); + attachExistingDownload(); + } + + private void attachExistingDownload() + { + Debug.Assert(beatmapSet != null); + var download = beatmapDownloader.GetExistingDownload(beatmapSet); + + if (download != null) + { + Enabled.Value = false; + TooltipText = string.Empty; + + download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100, Easing.OutQuint); + download.Failure += _ => attachExistingDownload(); + } + else + { + Enabled.Value = true; + TooltipText = "Update beatmap with online changes"; + + progressFill.ResizeWidthTo(0, 100, Easing.OutQuint); + } + } + } +} From 04d8bafdcee3c5b0a6a33e0046ced17f611da53f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:16:10 -0500 Subject: [PATCH 11/92] Implement beatmap difficulty panel design --- ...TestSceneBeatmapCarouselDifficultyPanel.cs | 101 +++++ osu.Game/Screens/SelectV2/BeatmapPanel.cs | 410 ++++++++++++++++-- 2 files changed, 463 insertions(+), 48 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs new file mode 100644 index 0000000000..c0ecb06085 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs @@ -0,0 +1,101 @@ +// 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.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.SelectV2; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneBeatmapCarouselDifficultyPanel : ThemeComparisonTestScene + { + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + private BeatmapInfo beatmap = null!; + + public TestSceneBeatmapCarouselDifficultyPanel() + : base(false) + { + } + + [Test] + public void TestDisplay() + { + AddStep("set beatmap", () => + { + var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) + ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) + ?? TestResources.CreateTestBeatmapSetInfo(); + + beatmap = beatmapSet.Beatmaps.First(); + CreateThemedContent(OverlayColourScheme.Aquamarine); + }); + } + + [Test] + public void TestRandomBeatmap() + { + AddStep("random beatmap", () => + { + beatmap = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()) + .First().Beatmaps.OrderBy(_ => RNG.Next()).First(); + CreateThemedContent(OverlayColourScheme.Aquamarine); + }); + } + + [Test] + public void TestManiaRuleset() + { + AddToggleStep("mania ruleset", v => Ruleset.Value = v ? new ManiaRuleset().RulesetInfo : new OsuRuleset().RulesetInfo); + } + + protected override Drawable CreateContent() + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + new BeatmapPanel + { + Item = new CarouselItem(beatmap) + }, + new BeatmapPanel + { + Item = new CarouselItem(beatmap), + KeyboardSelected = { Value = true } + }, + new BeatmapPanel + { + Item = new CarouselItem(beatmap), + Selected = { Value = true } + }, + new BeatmapPanel + { + Item = new CarouselItem(beatmap), + KeyboardSelected = { Value = true }, + Selected = { Value = true } + }, + } + }; + } + } +} diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index 2fe509402b..180acffe80 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -1,16 +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 System.Collections.Generic; using System.Diagnostics; +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; +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.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -18,68 +31,234 @@ namespace osu.Game.Screens.SelectV2 { public partial class BeatmapPanel : PoolableDrawable, ICarouselPanel { - [Resolved] - private BeatmapCarousel carousel { get; set; } = null!; + public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; + private const float colour_box_width = 30; + private const float corner_radius = 10; + + private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. + private const float difficulty_x_offset = 50f; // constant X offset for beatmap difficulty panels specifically. + private const float preselected_x_offset = 25f; + private const float selected_x_offset = 50f; + + private const float duration = 500; + + [Resolved] + private BeatmapCarousel? carousel { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private IBindable> mods { get; set; } = null!; + + private Container panel = null!; + private StarCounter starCounter = null!; + private ConstrainedIconContainer iconContainer = null!; + private Box hoverLayer = null!; private Box activationFlash = null!; - private OsuSpriteText text = null!; + + private Box backgroundBorder = null!; + + private StarRatingDisplay starRatingDisplay = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + + private OsuSpriteText keyCountText = null!; + + private IBindable? starDifficultyBindable; + private CancellationTokenSource? starDifficultyCancellationSource; + + private Container rightContainer = null!; + private Box starRatingGradient = null!; + private TopLocalRankV2 difficultyRank = null!; + private OsuSpriteText difficultyText = null!; + private OsuSpriteText authorText = null!; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + + RelativeSizeAxes = Axes.X; + Width = 0.9f; + Height = HEIGHT; + + InternalChild = panel = new Container + { + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(1f), + Radius = 10, + }, + Children = new Drawable[] + { + new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundBorder = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.ForStarDifficulty(0), + EdgeSmoothness = new Vector2(2, 0), + }, + rightContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.X, + Height = HEIGHT, + X = colour_box_width, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4), + }, + starRatingGradient = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }, + }, + } + }, + iconContainer = new ConstrainedIconContainer + { + X = colour_box_width / 2, + Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Size = new Vector2(20), + Colour = colourProvider.Background5, + }, + new FillFlowContainer + { + Padding = new MarginPadding { Top = 8, Left = colour_box_width + corner_radius }, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + difficultyRank = new TopLocalRankV2 + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.75f) + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.4f) + } + } + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new[] + { + keyCountText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + }, + difficultyText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 8f }, + }, + authorText = new OsuSpriteText + { + Colour = colourProvider.Content2, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft + } + } + } + } + }, + hoverLayer = new Box + { + Colour = colours.Blue.Opacity(0.1f), + Alpha = 0, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }, + activationFlash = new Box + { + Blending = BlendingParameters.Additive, + Alpha = 0f, + RelativeSizeAxes = Axes.Both, + }, + new HoverSounds(), + } + }; + } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { - var inputRectangle = DrawRectangle; + var inputRectangle = panel.DrawRectangle; // Cover the gaps introduced by the spacing between BeatmapPanels. inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); + return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - Size = new Vector2(500, CarouselItem.DEFAULT_HEIGHT); - Masking = true; + base.LoadComplete(); - InternalChildren = new Drawable[] + ruleset.BindValueChanged(_ => { - new Box - { - Colour = Color4.Aqua.Darken(5), - Alpha = 0.8f, - RelativeSizeAxes = Axes.Both, - }, - activationFlash = new Box - { - Colour = Color4.White, - Blending = BlendingParameters.Additive, - Alpha = 0, - RelativeSizeAxes = Axes.Both, - }, - text = new OsuSpriteText - { - Padding = new MarginPadding(5), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - } - }; - - Selected.BindValueChanged(value => - { - activationFlash.FadeTo(value.NewValue ? 0.2f : 0, 500, Easing.OutQuint); + computeStarRating(); + updateKeyCount(); }); - KeyboardSelected.BindValueChanged(value => + mods.BindValueChanged(_ => { - if (value.NewValue) - { - BorderThickness = 5; - BorderColour = Color4.Pink; - } - else - { - BorderThickness = 0; - } - }); + computeStarRating(); + updateKeyCount(); + }, true); + + Selected.BindValueChanged(_ => updateSelectionDisplay(), true); + KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); } protected override void PrepareForUse() @@ -89,13 +268,145 @@ namespace osu.Game.Screens.SelectV2 Debug.Assert(Item != null); var beatmap = (BeatmapInfo)Item.Model; - text.Text = $"Difficulty: {beatmap.DifficultyName} ({beatmap.StarRating:N1}*)"; + iconContainer.Icon = beatmap.Ruleset.CreateInstance().CreateIcon(); - this.FadeInFromZero(500, Easing.OutQuint); + difficultyRank.Beatmap = beatmap; + difficultyText.Text = beatmap.DifficultyName; + authorText.Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmap.Metadata.Author.Username); + + starDifficultyBindable = null; + + computeStarRating(); + updateKeyCount(); + + updateSelectionDisplay(); + FinishTransforms(true); + + this.FadeInFromZero(duration, Easing.OutQuint); + + // todo: only do this when visible. + // starCounter.ReplayAnimation(); + } + + private void updateSelectionDisplay() + { + bool selected = Selected.Value; + + rightContainer.ResizeHeightTo(selected ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint); + + updatePanelPosition(); + updateEdgeEffectColour(); + updateHover(); + } + + private void updateKeyboardSelectedDisplay() + { + updatePanelPosition(); + updateHover(); + } + + private void updatePanelPosition() + { + float x = glow_offset + difficulty_x_offset + selected_x_offset + preselected_x_offset; + + if (Selected.Value) + x -= selected_x_offset; + + if (KeyboardSelected.Value) + x -= preselected_x_offset; + + this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); + } + + private void updateHover() + { + bool hovered = IsHovered || (KeyboardSelected.Value && !Selected.Value); + + if (hovered) + hoverLayer.FadeIn(100, Easing.OutQuint); + else + hoverLayer.FadeOut(1000, Easing.OutQuint); + } + + private void computeStarRating() + { + starDifficultyCancellationSource?.Cancel(); + starDifficultyCancellationSource = new CancellationTokenSource(); + + if (Item == null) + return; + + var beatmap = (BeatmapInfo)Item.Model; + + starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token); + starDifficultyBindable.BindValueChanged(d => + { + var value = d.NewValue ?? default; + + starRatingDisplay.Current.Value = value; + starCounter.Current = (float)value.Stars; + + iconContainer.FadeColour(value.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); + + var starRatingColour = colours.ForStarDifficulty(value.Stars); + + backgroundBorder.FadeColour(starRatingColour, duration, Easing.OutQuint); + starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint); + starRatingGradient.FadeColour(ColourInfo.GradientHorizontal(starRatingColour.Opacity(0.25f), starRatingColour.Opacity(0)), duration, Easing.OutQuint); + starRatingGradient.FadeIn(duration, Easing.OutQuint); + + // todo: this doesn't work for dark star rating colours, still not sure how to fix. + activationFlash.FadeColour(starRatingColour, duration, Easing.OutQuint); + + updateEdgeEffectColour(); + }, true); + } + + private void updateEdgeEffectColour() + { + panel.FadeEdgeEffectTo(Selected.Value + ? colours.ForStarDifficulty(starDifficultyBindable?.Value?.Stars ?? 0f).Opacity(0.5f) + : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); + } + + private void updateKeyCount() + { + if (Item == null) + return; + + var beatmap = (BeatmapInfo)Item.Model; + + if (ruleset.Value.OnlineID == 3) + { + // Account for mania differences locally for now. + // Eventually this should be handled in a more modular way, allowing rulesets to add more information to the panel. + ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance(); + int keyCount = legacyRuleset.GetKeyCount(beatmap, mods.Value); + + keyCountText.Alpha = 1; + keyCountText.Text = $"[{keyCount}K] "; + } + else + keyCountText.Alpha = 0; + } + + protected override bool OnHover(HoverEvent e) + { + updateHover(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateHover(); + base.OnHoverLost(e); } protected override bool OnClick(ClickEvent e) { + if (carousel == null) + return true; + if (carousel.CurrentSelection != Item!.Model) { carousel.CurrentSelection = Item!.Model; @@ -115,7 +426,10 @@ namespace osu.Game.Screens.SelectV2 public double DrawYPosition { get; set; } - public void Activated() => activationFlash.FadeOutFromOne(500, Easing.OutQuint); + public void Activated() + { + activationFlash.FadeOutFromOne(500, Easing.OutQuint); + } #endregion } From 696366f8cb13c2dd1ee6f3b02c8c2d7f806d9126 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:17:13 -0500 Subject: [PATCH 12/92] Implement beatmap "standalone" panel design --- ...TestSceneBeatmapCarouselStandalonePanel.cs | 101 ++++ .../SelectV2/BeatmapStandalonePanel.cs | 460 ++++++++++++++++++ 2 files changed, 561 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs create mode 100644 osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs new file mode 100644 index 0000000000..76dcfc9507 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs @@ -0,0 +1,101 @@ +// 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.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.SelectV2; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneBeatmapCarouselStandalonePanel : ThemeComparisonTestScene + { + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + private BeatmapInfo beatmap = null!; + + public TestSceneBeatmapCarouselStandalonePanel() + : base(false) + { + } + + [Test] + public void TestDisplay() + { + AddStep("set beatmap", () => + { + var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) + ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) + ?? TestResources.CreateTestBeatmapSetInfo(); + + beatmap = beatmapSet.Beatmaps.First(); + CreateThemedContent(OverlayColourScheme.Aquamarine); + }); + } + + [Test] + public void TestRandomBeatmap() + { + AddStep("random beatmap", () => + { + beatmap = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()) + .First().Beatmaps.OrderBy(_ => RNG.Next()).First(); + CreateThemedContent(OverlayColourScheme.Aquamarine); + }); + } + + [Test] + public void TestManiaRuleset() + { + AddToggleStep("mania ruleset", v => Ruleset.Value = v ? new ManiaRuleset().RulesetInfo : new OsuRuleset().RulesetInfo); + } + + protected override Drawable CreateContent() + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + new BeatmapStandalonePanel + { + Item = new CarouselItem(beatmap) + }, + new BeatmapStandalonePanel + { + Item = new CarouselItem(beatmap), + KeyboardSelected = { Value = true } + }, + new BeatmapStandalonePanel + { + Item = new CarouselItem(beatmap), + Selected = { Value = true } + }, + new BeatmapStandalonePanel + { + Item = new CarouselItem(beatmap), + KeyboardSelected = { Value = true }, + Selected = { Value = true } + }, + } + }; + } + } +} diff --git a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs new file mode 100644 index 0000000000..11fa22ab09 --- /dev/null +++ b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs @@ -0,0 +1,460 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +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.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class BeatmapStandalonePanel : PoolableDrawable, ICarouselPanel + { + public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; + + private const float difficulty_icon_container_width = 30; + private const float corner_radius = 10; + + private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. + private const float preselected_x_offset = 25f; + private const float selected_x_offset = 50f; + + private const float duration = 500; + + [Resolved] + private BeatmapCarousel? carousel { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private IBindable> mods { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + + private IBindable? starDifficultyBindable; + private CancellationTokenSource? starDifficultyCancellationSource; + + private Container panel = null!; + private Box backgroundBorder = null!; + private BeatmapSetPanelBackground background = null!; + private Container backgroundContainer = null!; + private FillFlowContainer mainFlowContainer = null!; + private Box hoverLayer = null!; + + private OsuSpriteText titleText = null!; + private OsuSpriteText artistText = null!; + private UpdateBeatmapSetButtonV2 updateButton = null!; + private BeatmapSetOnlineStatusPill statusPill = null!; + + private ConstrainedIconContainer difficultyIcon = null!; + private FillFlowContainer difficultyLine = null!; + private StarRatingDisplay difficultyStarRating = null!; + private TopLocalRankV2 difficultyRank = null!; + private OsuSpriteText difficultyKeyCountText = null!; + private OsuSpriteText difficultyName = null!; + private OsuSpriteText difficultyAuthor = null!; + + [BackgroundDependencyLoader] + private void load() + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + RelativeSizeAxes = Axes.X; + Width = 1f; + Height = HEIGHT; + + InternalChild = panel = new Container + { + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 10, + }, + Children = new Drawable[] + { + new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundBorder = new Box + { + RelativeSizeAxes = Axes.Y, + Alpha = 0, + EdgeSmoothness = new Vector2(2, 0), + }, + backgroundContainer = new Container + { + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.X, + MaskingSmoothness = 2, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + background = new BeatmapSetPanelBackground + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + }, + }, + } + }, + difficultyIcon = new ConstrainedIconContainer + { + X = difficulty_icon_container_width / 2, + Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Size = new Vector2(20), + }, + mainFlowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + artistText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5f }, + Children = new Drawable[] + { + updateButton = new UpdateBeatmapSetButtonV2 + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f, Top = -2f }, + }, + statusPill = new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Margin = new MarginPadding { Right = 5f }, + }, + difficultyLine = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Scale = new Vector2(8f / 9f), + Margin = new MarginPadding { Right = 5f }, + }, + difficultyRank = new TopLocalRankV2 + { + Scale = new Vector2(8f / 11), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f }, + }, + difficultyKeyCountText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + Margin = new MarginPadding { Bottom = 2f }, + }, + difficultyName = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 5f, Bottom = 2f }, + }, + difficultyAuthor = new OsuSpriteText + { + Colour = colourProvider.Content2, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 5f, Bottom = 2f }, + } + } + }, + }, + } + } + }, + hoverLayer = new Box + { + Colour = colours.Blue.Opacity(0.1f), + Alpha = 0, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }, + new HoverSounds(), + } + }; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = panel.DrawRectangle; + + // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + + return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ruleset.BindValueChanged(_ => + { + computeStarRating(); + updateKeyCount(); + }); + + mods.BindValueChanged(_ => + { + computeStarRating(); + updateKeyCount(); + }, true); + + Selected.BindValueChanged(_ => updateSelectedDisplay(), true); + KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + Debug.Assert(Item != null); + + var beatmap = (BeatmapInfo)Item.Model; + var beatmapSet = beatmap.BeatmapSet!; + + // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). + background.Beatmap = beatmaps.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID)); + + titleText.Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title); + artistText.Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist); + updateButton.BeatmapSet = beatmapSet; + statusPill.Status = beatmapSet.Status; + + difficultyIcon.Icon = beatmap.Ruleset.CreateInstance().CreateIcon(); + difficultyIcon.Show(); + + difficultyRank.Beatmap = beatmap; + difficultyName.Text = beatmap.DifficultyName; + difficultyAuthor.Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmap.Metadata.Author.Username); + difficultyLine.Show(); + + computeStarRating(); + + updateSelectedDisplay(); + FinishTransforms(true); + + this.FadeInFromZero(duration, Easing.OutQuint); + } + + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + + background.Beatmap = null; + updateButton.BeatmapSet = null; + difficultyRank.Beatmap = null; + starDifficultyBindable = null; + } + + private void updateSelectedDisplay() + { + if (Item == null) + return; + + updatePanelPosition(); + + backgroundBorder.RelativeSizeAxes = Selected.Value ? Axes.Both : Axes.Y; + backgroundBorder.Width = Selected.Value ? 1 : difficulty_icon_container_width + corner_radius; + backgroundBorder.FadeTo(Selected.Value ? 1 : 0, duration, Easing.OutQuint); + difficultyIcon.FadeTo(Selected.Value ? 1 : 0, duration, Easing.OutQuint); + + backgroundContainer.ResizeHeightTo(Selected.Value ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint); + backgroundContainer.MoveToX(Selected.Value ? difficulty_icon_container_width : 0, duration, Easing.OutQuint); + mainFlowContainer.MoveToX(Selected.Value ? difficulty_icon_container_width : 0, duration, Easing.OutQuint); + + panel.EdgeEffect = panel.EdgeEffect with { Radius = Selected.Value ? 15 : 10 }; + updateEdgeEffectColour(); + } + + private void updateKeyboardSelectedDisplay() + { + updatePanelPosition(); + updateHover(); + } + + private void updatePanelPosition() + { + float x = glow_offset + selected_x_offset + preselected_x_offset; + + if (Selected.Value) + x -= selected_x_offset; + + if (KeyboardSelected.Value) + x -= preselected_x_offset; + + this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); + } + + private void updateHover() + { + bool hovered = IsHovered || KeyboardSelected.Value; + + if (hovered) + hoverLayer.FadeIn(100, Easing.OutQuint); + else + hoverLayer.FadeOut(1000, Easing.OutQuint); + } + + private void computeStarRating() + { + starDifficultyCancellationSource?.Cancel(); + starDifficultyCancellationSource = new CancellationTokenSource(); + + if (Item == null) + return; + + var beatmap = (BeatmapInfo)Item.Model; + + starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token); + starDifficultyBindable.BindValueChanged(d => + { + var value = d.NewValue ?? default; + + backgroundBorder.FadeColour(colours.ForStarDifficulty(value.Stars), duration, Easing.OutQuint); + difficultyIcon.FadeColour(value.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); + difficultyStarRating.Current.Value = value; + + updateEdgeEffectColour(); + }, true); + } + + private void updateEdgeEffectColour() + { + panel.FadeEdgeEffectTo(Selected.Value + ? colours.ForStarDifficulty(starDifficultyBindable?.Value?.Stars ?? 0f).Opacity(0.5f) + : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); + } + + private void updateKeyCount() + { + if (Item == null) + return; + + var beatmap = (BeatmapInfo)Item.Model; + + if (ruleset.Value.OnlineID == 3) + { + // Account for mania differences locally for now. + // Eventually this should be handled in a more modular way, allowing rulesets to add more information to the panel. + ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance(); + int keyCount = legacyRuleset.GetKeyCount(beatmap, mods.Value); + + difficultyKeyCountText.Alpha = 1; + difficultyKeyCountText.Text = $"[{keyCount}K] "; + } + else + difficultyKeyCountText.Alpha = 0; + } + + protected override bool OnHover(HoverEvent e) + { + updateHover(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateHover(); + base.OnHoverLost(e); + } + + protected override bool OnClick(ClickEvent e) + { + if (carousel != null) + carousel.CurrentSelection = Item!.Model; + + return true; + } + + #region ICarouselPanel + + public CarouselItem? Item { get; set; } + public BindableBool Selected { get; } = new BindableBool(); + public BindableBool Expanded { get; } = new BindableBool(); + public BindableBool KeyboardSelected { get; } = new BindableBool(); + + public double DrawYPosition { get; set; } + + public void Activated() + { + // sets should never be activated. + throw new InvalidOperationException(); + } + + #endregion + } +} From c94d11b7fe08b7d2284615049dd0c0f9de14c5d7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:19:12 -0500 Subject: [PATCH 13/92] Add beatmap carousel to new song select screen --- osu.Game/Screens/SelectV2/SongSelectV2.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Screens/SelectV2/SongSelectV2.cs b/osu.Game/Screens/SelectV2/SongSelectV2.cs index 2f9667793f..88825d96e0 100644 --- a/osu.Game/Screens/SelectV2/SongSelectV2.cs +++ b/osu.Game/Screens/SelectV2/SongSelectV2.cs @@ -39,6 +39,20 @@ namespace osu.Game.Screens.SelectV2 { AddRangeInternal(new Drawable[] { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = ScreenFooter.HEIGHT }, + Child = new BeatmapCarousel + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + // Push the carousel slightly off the right edge of the screen for the ends of the panels to be cut off. + X = 20f, + }, + }, modSelectOverlay, }); } From 29882a2542bb9895a163461055ff7f57f961f022 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 07:19:14 -0500 Subject: [PATCH 14/92] Allow importing real beatmaps in song select test scene --- .../SongSelectV2/TestSceneSongSelect.cs | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index d43026c960..33474d7449 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -9,15 +9,27 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Taiko; using osu.Game.Screens; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; using osu.Game.Screens.SelectV2.Footer; +using osu.Game.Tests.Resources; using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelectV2 @@ -30,6 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Cached] private readonly OsuLogo logo; + private BeatmapManager beatmapManager = null!; + + protected override bool UseOnlineAPI => true; + public TestSceneSongSelect() { Children = new Drawable[] @@ -49,6 +65,35 @@ namespace osu.Game.Tests.Visual.SongSelectV2 }; } + [BackgroundDependencyLoader] + private void load(GameHost host, IAPIProvider onlineAPI) + { + BeatmapStore beatmapStore; + BeatmapUpdater beatmapUpdater; + BeatmapDifficultyCache difficultyCache; + + // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. + // At a point we have isolated interactive test runs enough, this can likely be removed. + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(Realm); + Dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, onlineAPI, Audio, Resources, host, Beatmap.Default, difficultyCache)); + Dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(beatmapManager, difficultyCache, onlineAPI, LocalStorage)); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); + + beatmapManager.ProcessBeatmap = (set, scope) => beatmapUpdater.Process(set, scope); + + MusicController music; + Dependencies.Cache(music = new MusicController()); + + // required to get bindables attached + Add(difficultyCache); + Add(music); + Add(beatmapStore); + + Dependencies.Cache(new OsuConfigManager(LocalStorage)); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -64,6 +109,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SongSelectV2())); AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelectV2 songSelect && songSelect.IsLoaded); + AddStep("import test beatmap", () => beatmapManager.Import(TestResources.GetTestBeatmapForImport())); + } + + [Test] + public void TestRulesets() + { + AddStep("set osu ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("set catch ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("set mania ruleset", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); } #region Footer @@ -80,8 +135,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddStep("modified", () => SelectedMods.Value = new List { new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); AddStep("modified + one", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); AddStep("modified + two", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); - AddStep("modified + three", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); - AddStep("modified + four", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); + AddStep("modified + three", + () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); + AddStep("modified + four", + () => SelectedMods.Value = new List + { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); AddWaitStep("wait", 3); From f9962f95f098bf3e4076839544090ad7556c3fcd Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 08:16:51 -0500 Subject: [PATCH 15/92] Implement group panel design --- .../TestSceneBeatmapCarouselGroupPanel.cs | 80 +++++ osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 1 + osu.Game/Screens/SelectV2/GroupPanel.cs | 220 ++++++++++--- osu.Game/Screens/SelectV2/StarsGroupPanel.cs | 288 ++++++++++++++++++ 4 files changed, 541 insertions(+), 48 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs create mode 100644 osu.Game/Screens/SelectV2/StarsGroupPanel.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs new file mode 100644 index 0000000000..eea3870117 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.SelectV2; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneBeatmapCarouselGroupPanel : ThemeComparisonTestScene + { + public TestSceneBeatmapCarouselGroupPanel() + : base(false) + { + } + + protected override Drawable CreateContent() + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + new GroupPanel + { + Item = new CarouselItem(new GroupDefinition("Group A")) + }, + new GroupPanel + { + Item = new CarouselItem(new GroupDefinition("Group A")), + KeyboardSelected = { Value = true } + }, + new GroupPanel + { + Item = new CarouselItem(new GroupDefinition("Group A")), + Selected = { Value = true } + }, + new GroupPanel + { + Item = new CarouselItem(new GroupDefinition("Group A")), + KeyboardSelected = { Value = true }, + Selected = { Value = true } + }, + new StarsGroupPanel + { + Item = new CarouselItem(new StarsGroupDefinition(1)) + }, + new StarsGroupPanel + { + Item = new CarouselItem(new StarsGroupDefinition(3)), + }, + new StarsGroupPanel + { + Item = new CarouselItem(new StarsGroupDefinition(5)), + }, + new StarsGroupPanel + { + Item = new CarouselItem(new StarsGroupDefinition(7)), + }, + new StarsGroupPanel + { + Item = new CarouselItem(new StarsGroupDefinition(8)), + }, + new StarsGroupPanel + { + Item = new CarouselItem(new StarsGroupDefinition(9)), + }, + } + }; + } + } +} diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 12660d8642..a49dcdd86c 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -264,4 +264,5 @@ namespace osu.Game.Screens.SelectV2 } public record GroupDefinition(string Title); + public record StarsGroupDefinition(int StarNumber); } diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index df930a3111..8995b93290 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -7,10 +7,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -18,15 +22,20 @@ namespace osu.Game.Screens.SelectV2 { public partial class GroupPanel : PoolableDrawable, ICarouselPanel { - public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 2; + public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; + + private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. + private const float preselected_x_offset = 25f; + private const float selected_x_offset = 50f; + + private const float duration = 500; [Resolved] - private BeatmapCarousel carousel { get; set; } = null!; + private BeatmapCarousel? carousel { get; set; } private Box activationFlash = null!; - private OsuSpriteText text = null!; - - private Box box = null!; + private OsuSpriteText titleText = null!; + private Box hoverLayer = null!; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { @@ -39,56 +48,128 @@ namespace osu.Game.Screens.SelectV2 } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - Size = new Vector2(500, HEIGHT); - Masking = true; + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + RelativeSizeAxes = Axes.X; + Height = HEIGHT; - InternalChildren = new Drawable[] + InternalChild = new Container { - box = new Box + RelativeSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] { - Colour = Color4.DarkBlue.Darken(5), - Alpha = 0.8f, - RelativeSizeAxes = Axes.Both, - }, - activationFlash = new Box - { - Colour = Color4.White, - Blending = BlendingParameters.Additive, - Alpha = 0, - RelativeSizeAxes = Axes.Both, - }, - text = new OsuSpriteText - { - Padding = new MarginPadding(5), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10f }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + } + } + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10f }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + titleText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + X = 10f, + }, + new CircularContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 30f }, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), + // TODO: requires Carousel/CarouselItem-side implementation + Text = "43", + UseFullGlyphHeight = false, + } + }, + }, + } + } + }, + activationFlash = new Box + { + Colour = Color4.White, + Blending = BlendingParameters.Additive, + Alpha = 0, + RelativeSizeAxes = Axes.Both, + }, + hoverLayer = new Box + { + Colour = colours.Blue.Opacity(0.1f), + Alpha = 0, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }, + new HoverSounds(), } }; + } - Selected.BindValueChanged(value => - { - activationFlash.FadeTo(value.NewValue ? 0.2f : 0, 500, Easing.OutQuint); - }); + protected override void LoadComplete() + { + base.LoadComplete(); - Expanded.BindValueChanged(value => - { - box.FadeColour(value.NewValue ? Color4.SkyBlue : Color4.DarkBlue.Darken(5), 500, Easing.OutQuint); - }); + Expanded.BindValueChanged(_ => updateExpandedDisplay(), true); + KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + } - KeyboardSelected.BindValueChanged(value => - { - if (value.NewValue) - { - BorderThickness = 5; - BorderColour = Color4.Pink; - } - else - { - BorderThickness = 0; - } - }); + private void updateExpandedDisplay() + { + updatePanelPosition(); + + // todo: figma shares no extra visual feedback on this. + + activationFlash.FadeTo(0.2f).FadeTo(0f, 500, Easing.OutQuint); } protected override void PrepareForUse() @@ -99,17 +180,60 @@ namespace osu.Game.Screens.SelectV2 GroupDefinition group = (GroupDefinition)Item.Model; - text.Text = group.Title; + titleText.Text = group.Title; this.FadeInFromZero(500, Easing.OutQuint); } protected override bool OnClick(ClickEvent e) { - carousel.CurrentSelection = Item!.Model; + if (carousel != null) + carousel.CurrentSelection = Item!.Model; + return true; } + private void updateKeyboardSelectedDisplay() + { + updatePanelPosition(); + updateHover(); + } + + private void updatePanelPosition() + { + float x = glow_offset + selected_x_offset + preselected_x_offset; + + if (Expanded.Value) + x -= selected_x_offset; + + if (KeyboardSelected.Value) + x -= preselected_x_offset; + + this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); + } + + private void updateHover() + { + bool hovered = IsHovered || KeyboardSelected.Value; + + if (hovered) + hoverLayer.FadeIn(100, Easing.OutQuint); + else + hoverLayer.FadeOut(1000, Easing.OutQuint); + } + + protected override bool OnHover(HoverEvent e) + { + updateHover(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateHover(); + base.OnHoverLost(e); + } + #region ICarouselPanel public CarouselItem? Item { get; set; } diff --git a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs new file mode 100644 index 0000000000..8ebf3fc7e8 --- /dev/null +++ b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs @@ -0,0 +1,288 @@ +// 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.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class StarsGroupPanel : PoolableDrawable, ICarouselPanel + { + public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; + + private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. + private const float preselected_x_offset = 25f; + private const float expanded_x_offset = 50f; + + private const float duration = 500; + + [Resolved] + private BeatmapCarousel? carousel { get; set; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private Box activationFlash = null!; + private Box outerLayer = null!; + private Box innerLayer = null!; + private StarRatingDisplay starRatingDisplay = null!; + private StarCounter starCounter = null!; + private Box hoverLayer = null!; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = DrawRectangle; + + // Cover a gap introduced by the spacing between a GroupPanel and a BeatmapPanel either below/above it. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + + return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); + } + + [BackgroundDependencyLoader] + private void load() + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + RelativeSizeAxes = Axes.X; + Height = HEIGHT; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10f }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + } + } + }, + outerLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10f }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] + { + innerLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.2f), + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f, 0f), + Margin = new MarginPadding { Left = 10f }, + Children = new Drawable[] + { + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(8f / 20f), + }, + } + }, + new CircularContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 30f }, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), + // TODO: requires Carousel/CarouselItem-side implementation + Text = "43", + UseFullGlyphHeight = false, + } + }, + }, + } + } + }, + activationFlash = new Box + { + Colour = Color4.White, + Blending = BlendingParameters.Additive, + Alpha = 0, + RelativeSizeAxes = Axes.Both, + }, + hoverLayer = new Box + { + Colour = colours.Blue.Opacity(0.1f), + Alpha = 0, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }, + new HoverSounds(), + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(_ => updateExpandedDisplay(), true); + KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + } + + private void updateExpandedDisplay() + { + updatePanelPosition(); + + // todo: figma shares no extra visual feedback on this. + + activationFlash.FadeTo(0.2f).FadeTo(0f, 500, Easing.OutQuint); + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + Debug.Assert(Item != null); + + StarsGroupDefinition group = (StarsGroupDefinition)Item.Model; + + Color4 colour = group.StarNumber >= 9 ? OsuColour.Gray(0.2f) : colours.ForStarDifficulty(group.StarNumber); + Color4 contentColour = group.StarNumber >= 7 ? colours.Orange1 : colourProvider.Background5; + + outerLayer.Colour = colour; + starCounter.Colour = contentColour; + + starRatingDisplay.Current.Value = new StarDifficulty(group.StarNumber, 0); + starCounter.Current = group.StarNumber; + + this.FadeInFromZero(500, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + if (carousel != null) + carousel.CurrentSelection = Item!.Model; + + return true; + } + + private void updateKeyboardSelectedDisplay() + { + updatePanelPosition(); + updateHover(); + } + + private void updatePanelPosition() + { + float x = glow_offset + expanded_x_offset + preselected_x_offset; + + if (Expanded.Value) + x -= expanded_x_offset; + + if (KeyboardSelected.Value) + x -= preselected_x_offset; + + this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); + } + + private void updateHover() + { + bool hovered = IsHovered || KeyboardSelected.Value; + + if (hovered) + hoverLayer.FadeIn(100, Easing.OutQuint); + else + hoverLayer.FadeOut(1000, Easing.OutQuint); + } + + protected override bool OnHover(HoverEvent e) + { + updateHover(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateHover(); + base.OnHoverLost(e); + } + + #region ICarouselPanel + + public CarouselItem? Item { get; set; } + public BindableBool Selected { get; } = new BindableBool(); + public BindableBool Expanded { get; } = new BindableBool(); + public BindableBool KeyboardSelected { get; } = new BindableBool(); + + public double DrawYPosition { get; set; } + + public void Activated() + { + // sets should never be activated. + throw new InvalidOperationException(); + } + + #endregion + } +} From 04a3ee863c3f1b2f93d4868da9aebb79d2ec2d32 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 08:38:08 -0500 Subject: [PATCH 16/92] Fix design tests --- ...TestSceneBeatmapCarouselDifficultyPanel.cs | 26 +++++++++++-------- .../TestSceneBeatmapCarouselSetPanel.cs | 21 +++++++++------ ...TestSceneBeatmapCarouselStandalonePanel.cs | 26 +++++++++++-------- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs index c0ecb06085..a9f73759f7 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs @@ -30,18 +30,20 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { } + [SetUp] + public void SetUp() => Schedule(() => + { + var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) + ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) + ?? TestResources.CreateTestBeatmapSetInfo(); + + beatmap = beatmapSet.Beatmaps.First(); + }); + [Test] public void TestDisplay() { - AddStep("set beatmap", () => - { - var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) - ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) - ?? TestResources.CreateTestBeatmapSetInfo(); - - beatmap = beatmapSet.Beatmaps.First(); - CreateThemedContent(OverlayColourScheme.Aquamarine); - }); + AddStep("display", () => CreateThemedContent(OverlayColourScheme.Aquamarine)); } [Test] @@ -49,8 +51,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("random beatmap", () => { - beatmap = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()) - .First().Beatmaps.OrderBy(_ => RNG.Next()).First(); + var randomSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).FirstOrDefault(); + randomSet ??= TestResources.CreateTestBeatmapSetInfo(); + beatmap = randomSet.Beatmaps.OrderBy(_ => RNG.Next()).First(); + CreateThemedContent(OverlayColourScheme.Aquamarine); }); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs index 6b981d7b33..8f7cac2b58 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs @@ -28,16 +28,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { } + [SetUp] + public void SetUp() => Schedule(() => + { + beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) + ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) + ?? TestResources.CreateTestBeatmapSetInfo(); + }); + [Test] public void TestDisplay() { - AddStep("set beatmap", () => - { - beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) - ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) - ?? TestResources.CreateTestBeatmapSetInfo(); - CreateThemedContent(OverlayColourScheme.Aquamarine); - }); + AddStep("display", () => CreateThemedContent(OverlayColourScheme.Aquamarine)); } [Test] @@ -45,7 +47,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("random beatmap", () => { - beatmapSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).First(); + var randomSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).FirstOrDefault(); + randomSet ??= TestResources.CreateTestBeatmapSetInfo(); + beatmapSet = randomSet; + CreateThemedContent(OverlayColourScheme.Aquamarine); }); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs index 76dcfc9507..a34ac31d5d 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs @@ -30,18 +30,20 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { } + [SetUp] + public void SetUp() => Schedule(() => + { + var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) + ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) + ?? TestResources.CreateTestBeatmapSetInfo(); + + beatmap = beatmapSet.Beatmaps.First(); + }); + [Test] public void TestDisplay() { - AddStep("set beatmap", () => - { - var beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526) - ?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected) - ?? TestResources.CreateTestBeatmapSetInfo(); - - beatmap = beatmapSet.Beatmaps.First(); - CreateThemedContent(OverlayColourScheme.Aquamarine); - }); + AddStep("display", () => CreateThemedContent(OverlayColourScheme.Aquamarine)); } [Test] @@ -49,8 +51,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("random beatmap", () => { - beatmap = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()) - .First().Beatmaps.OrderBy(_ => RNG.Next()).First(); + var randomSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).FirstOrDefault(); + randomSet ??= TestResources.CreateTestBeatmapSetInfo(); + beatmap = randomSet.Beatmaps.OrderBy(_ => RNG.Next()).First(); + CreateThemedContent(OverlayColourScheme.Aquamarine); }); } From 467ea91105249569887ba2c12be021d6292a372e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 21:47:15 -0500 Subject: [PATCH 17/92] Fix basic code quality issues --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 1 + osu.Game/Screens/SelectV2/StarsGroupPanel.cs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index a49dcdd86c..4de0041d36 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -264,5 +264,6 @@ namespace osu.Game.Screens.SelectV2 } public record GroupDefinition(string Title); + public record StarsGroupDefinition(int StarNumber); } diff --git a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs index 8ebf3fc7e8..76e3da2500 100644 --- a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs +++ b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs @@ -43,7 +43,6 @@ namespace osu.Game.Screens.SelectV2 private Box activationFlash = null!; private Box outerLayer = null!; - private Box innerLayer = null!; private StarRatingDisplay starRatingDisplay = null!; private StarCounter starCounter = null!; private Box hoverLayer = null!; @@ -108,7 +107,7 @@ namespace osu.Game.Screens.SelectV2 Masking = true, Children = new Drawable[] { - innerLayer = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black.Opacity(0.2f), From 72a62b70c469407af7a91c7933ec7d6c803858da Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 22:14:38 -0500 Subject: [PATCH 18/92] Simplify some code --- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 10 ++++++---- osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 8 +++++--- osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs | 7 +++++-- osu.Game/Screens/SelectV2/GroupPanel.cs | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index 180acffe80..896b8ea82a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -36,8 +36,10 @@ namespace osu.Game.Screens.SelectV2 private const float colour_box_width = 30; private const float corner_radius = 10; - private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. - private const float difficulty_x_offset = 50f; // constant X offset for beatmap difficulty panels specifically. + // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel + // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). + private const float difficulty_x_offset = 80f; // constant X offset for beatmap difficulty panels specifically. + private const float preselected_x_offset = 25f; private const float selected_x_offset = 50f; @@ -89,7 +91,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.TopRight; RelativeSizeAxes = Axes.X; - Width = 0.9f; + Width = 1f; Height = HEIGHT; InternalChild = panel = new Container @@ -307,7 +309,7 @@ namespace osu.Game.Screens.SelectV2 private void updatePanelPosition() { - float x = glow_offset + difficulty_x_offset + selected_x_offset + preselected_x_offset; + float x = difficulty_x_offset + selected_x_offset + preselected_x_offset; if (Selected.Value) x -= selected_x_offset; diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index 4706ea487a..dff563339c 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -33,8 +33,10 @@ namespace osu.Game.Screens.SelectV2 private const float arrow_container_width = 20; private const float corner_radius = 10; - private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. - private const float set_x_offset = 20f; // constant X offset for beatmap set panels specifically. + // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel + // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). + private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. + private const float preselected_x_offset = 25f; private const float expanded_x_offset = 50f; @@ -269,7 +271,7 @@ namespace osu.Game.Screens.SelectV2 private void updatePanelPosition() { - float x = glow_offset + set_x_offset + expanded_x_offset + preselected_x_offset; + float x = set_x_offset + expanded_x_offset + preselected_x_offset; if (Expanded.Value) x -= expanded_x_offset; diff --git a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs index 11fa22ab09..c3a773799a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs @@ -38,7 +38,10 @@ namespace osu.Game.Screens.SelectV2 private const float difficulty_icon_container_width = 30; private const float corner_radius = 10; - private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. + // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel + // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). + private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. + private const float preselected_x_offset = 25f; private const float selected_x_offset = 50f; @@ -348,7 +351,7 @@ namespace osu.Game.Screens.SelectV2 private void updatePanelPosition() { - float x = glow_offset + selected_x_offset + preselected_x_offset; + float x = set_x_offset + selected_x_offset + preselected_x_offset; if (Selected.Value) x -= selected_x_offset; diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index 8995b93290..10d3b8934e 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. + private const float glow_offset = 10f; // extra space for any edge effect to not be cutoff by the right edge of the carousel. private const float preselected_x_offset = 25f; private const float selected_x_offset = 50f; From 5e894a6f7e6faff59840d6b923ebd509a32853ee Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 22:24:36 -0500 Subject: [PATCH 19/92] Fix carousel tests failing due to X offsets --- .../TestSceneBeatmapCarouselV2GroupSelection.cs | 10 +++++----- .../TestSceneBeatmapCarouselV2Selection.cs | 13 ++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs index ebdc54864e..4e6aa5d6c2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs @@ -165,26 +165,26 @@ namespace osu.Game.Tests.Visual.SongSelect WaitForDrawablePanels(); SelectNextGroup(); - clickOnPanel(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + clickOnPanel(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(p.LayoutRectangle.Centre.X, -1f)); WaitForGroupSelection(0, 1); - clickOnPanel(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + clickOnPanel(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(p.LayoutRectangle.Centre.X, 1f)); WaitForGroupSelection(0, 0); SelectNextPanel(); Select(); WaitForGroupSelection(0, 1); - clickOnGroup(0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + clickOnGroup(0, p => p.LayoutRectangle.BottomLeft + new Vector2(p.LayoutRectangle.Centre.X, 1f)); AddAssert("group 0 collapsed", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(0).Expanded.Value, () => Is.False); clickOnGroup(0, p => p.LayoutRectangle.Centre); AddAssert("group 0 expanded", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(0).Expanded.Value, () => Is.True); AddStep("scroll to end", () => Scroll.ScrollToEnd(false)); - clickOnPanel(0, 4, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + clickOnPanel(0, 4, p => p.LayoutRectangle.BottomLeft + new Vector2(p.LayoutRectangle.Centre.X, 1f)); WaitForGroupSelection(0, 4); - clickOnGroup(1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + clickOnGroup(1, p => p.LayoutRectangle.TopLeft + new Vector2(p.LayoutRectangle.Centre.X, -1f)); AddAssert("group 1 expanded", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(1).Expanded.Value, () => Is.True); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs index 5541e217cf..3566b5e95f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs @@ -187,24 +187,23 @@ namespace osu.Game.Tests.Visual.SongSelect WaitForDrawablePanels(); SelectNextGroup(); - clickOnDifficulty(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + clickOnDifficulty(0, 1, p => new Vector2(p.LayoutRectangle.Centre.X, -1f)); WaitForSelection(0, 1); - clickOnDifficulty(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + clickOnDifficulty(0, 0, p => new Vector2(p.LayoutRectangle.Centre.X, BeatmapPanel.HEIGHT + 1f)); WaitForSelection(0, 0); - SelectNextPanel(); - Select(); + clickOnDifficulty(0, 1, p => p.LayoutRectangle.Centre); WaitForSelection(0, 1); - clickOnSet(0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + clickOnSet(0, p => new Vector2(p.LayoutRectangle.Centre.X, BeatmapSetPanel.HEIGHT + 1f)); WaitForSelection(0, 0); AddStep("scroll to end", () => Scroll.ScrollToEnd(false)); - clickOnDifficulty(0, 4, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + clickOnDifficulty(0, 4, p => new Vector2(p.LayoutRectangle.Centre.X, BeatmapPanel.HEIGHT + 1f)); WaitForSelection(0, 4); - clickOnSet(1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + clickOnSet(1, p => new Vector2(p.LayoutRectangle.Centre.X, -1f)); WaitForSelection(1, 0); } From aab4a79ce4e87ebc24481398b378cc939b64cd7c Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 22:37:03 -0500 Subject: [PATCH 20/92] Push all beatmap panels to hide their tails --- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 3 ++- osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 1 + .../Screens/SelectV2/BeatmapStandalonePanel.cs | 1 + osu.Game/Screens/SelectV2/GroupPanel.cs | 16 ++++++++++------ osu.Game/Screens/SelectV2/SongSelectV2.cs | 4 +--- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index 896b8ea82a..e5b612b1b2 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.SelectV2 // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). - private const float difficulty_x_offset = 80f; // constant X offset for beatmap difficulty panels specifically. + private const float difficulty_x_offset = 100f; // constant X offset for beatmap difficulty panels specifically. private const float preselected_x_offset = 25f; private const float selected_x_offset = 50f; @@ -99,6 +99,7 @@ namespace osu.Game.Screens.SelectV2 Masking = true, CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, + X = corner_radius, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index dff563339c..aabc39f27f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -81,6 +81,7 @@ namespace osu.Game.Screens.SelectV2 Masking = true, CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, + X = corner_radius, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs index c3a773799a..c0a5f828f4 100644 --- a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs @@ -105,6 +105,7 @@ namespace osu.Game.Screens.SelectV2 Masking = true, CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, + X = corner_radius, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index 10d3b8934e..b5fa338f82 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; + private const float corner_radius = 10; + private const float glow_offset = 10f; // extra space for any edge effect to not be cutoff by the right edge of the carousel. private const float preselected_x_offset = 25f; private const float selected_x_offset = 50f; @@ -33,18 +35,19 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private BeatmapCarousel? carousel { get; set; } + private Container panel = null!; private Box activationFlash = null!; private OsuSpriteText titleText = null!; private Box hoverLayer = null!; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { - var inputRectangle = DrawRectangle; + var inputRectangle = panel.DrawRectangle; // Cover a gap introduced by the spacing between a GroupPanel and a BeatmapPanel either below/above it. inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); + return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); } [BackgroundDependencyLoader] @@ -55,11 +58,12 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X; Height = HEIGHT; - InternalChild = new Container + InternalChild = panel = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = 10f, + CornerRadius = corner_radius, Masking = true, + X = corner_radius, Children = new Drawable[] { new Container @@ -69,7 +73,7 @@ namespace osu.Game.Screens.SelectV2 Child = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = 10f, + CornerRadius = corner_radius, Masking = true, Children = new Drawable[] { @@ -93,7 +97,7 @@ namespace osu.Game.Screens.SelectV2 Child = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = 10f, + CornerRadius = corner_radius, Masking = true, Children = new Drawable[] { diff --git a/osu.Game/Screens/SelectV2/SongSelectV2.cs b/osu.Game/Screens/SelectV2/SongSelectV2.cs index 88825d96e0..3943d059f9 100644 --- a/osu.Game/Screens/SelectV2/SongSelectV2.cs +++ b/osu.Game/Screens/SelectV2/SongSelectV2.cs @@ -48,9 +48,7 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Both, - Width = 0.5f, - // Push the carousel slightly off the right edge of the screen for the ends of the panels to be cut off. - X = 20f, + Width = 0.6f, }, }, modSelectOverlay, From ecc3aeadf2f5fbb17008ff1919ba9e68a0e0b77d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 22:39:42 -0500 Subject: [PATCH 21/92] Make `BeatmapPanel` appear hovered on keyboard selection even if selected Was an intentional choice but appeared weird to others instead. The feedback itself probably needs changing. --- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index e5b612b1b2..c36a23e51f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -299,7 +299,6 @@ namespace osu.Game.Screens.SelectV2 updatePanelPosition(); updateEdgeEffectColour(); - updateHover(); } private void updateKeyboardSelectedDisplay() @@ -323,7 +322,7 @@ namespace osu.Game.Screens.SelectV2 private void updateHover() { - bool hovered = IsHovered || (KeyboardSelected.Value && !Selected.Value); + bool hovered = IsHovered || KeyboardSelected.Value; if (hovered) hoverLayer.FadeIn(100, Easing.OutQuint); From 134e62c39afb3aa4a36d790c509aff24a7b5bead Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 6 Feb 2025 00:10:42 -0500 Subject: [PATCH 22/92] Abstractify beatmap panel piece and update all panel implementations --- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 249 +++---------- osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 278 ++++---------- .../SelectV2/BeatmapStandalonePanel.cs | 342 ++++++------------ .../Screens/SelectV2/CarouselPanelPiece.cs | 240 ++++++++++++ osu.Game/Screens/SelectV2/GroupPanel.cs | 212 +++-------- osu.Game/Screens/SelectV2/StarsGroupPanel.cs | 234 ++++-------- 6 files changed, 608 insertions(+), 947 deletions(-) create mode 100644 osu.Game/Screens/SelectV2/CarouselPanelPiece.cs diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index c36a23e51f..bd4cf6d7cf 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -6,13 +6,9 @@ using System.Diagnostics; 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; 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.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -25,7 +21,6 @@ using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { @@ -33,36 +28,23 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - private const float colour_box_width = 30; - private const float corner_radius = 10; - // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). private const float difficulty_x_offset = 100f; // constant X offset for beatmap difficulty panels specifically. - private const float preselected_x_offset = 25f; - private const float selected_x_offset = 50f; - private const float duration = 500; - [Resolved] - private BeatmapCarousel? carousel { get; set; } - - [Resolved] - private IBindable ruleset { get; set; } = null!; - - [Resolved] - private IBindable> mods { get; set; } = null!; - - private Container panel = null!; + private CarouselPanelPiece panel = null!; private StarCounter starCounter = null!; - private ConstrainedIconContainer iconContainer = null!; - private Box hoverLayer = null!; - private Box activationFlash = null!; - - private Box backgroundBorder = null!; - + private ConstrainedIconContainer difficultyIcon = null!; + private OsuSpriteText keyCountText = null!; private StarRatingDisplay starRatingDisplay = null!; + private TopLocalRankV2 difficultyRank = null!; + private OsuSpriteText difficultyText = null!; + private OsuSpriteText authorText = null!; + + private IBindable? starDifficultyBindable; + private CancellationTokenSource? starDifficultyCancellationSource; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -73,16 +55,24 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; - private OsuSpriteText keyCountText = null!; + [Resolved] + private BeatmapCarousel? carousel { get; set; } - private IBindable? starDifficultyBindable; - private CancellationTokenSource? starDifficultyCancellationSource; + [Resolved] + private IBindable ruleset { get; set; } = null!; - private Container rightContainer = null!; - private Box starRatingGradient = null!; - private TopLocalRankV2 difficultyRank = null!; - private OsuSpriteText difficultyText = null!; - private OsuSpriteText authorText = null!; + [Resolved] + private IBindable> mods { get; set; } = null!; + + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) + { + var inputRectangle = panel.TopLevelContent.DrawRectangle; + + // Cover the gaps introduced by the spacing between BeatmapPanels. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + + return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); + } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -94,67 +84,21 @@ namespace osu.Game.Screens.SelectV2 Width = 1f; Height = HEIGHT; - InternalChild = panel = new Container + InternalChild = panel = new CarouselPanelPiece(difficulty_x_offset) { - Masking = true, - CornerRadius = corner_radius, - RelativeSizeAxes = Axes.Both, - X = corner_radius, - EdgeEffect = new EdgeEffectParameters + Icon = difficultyIcon = new ConstrainedIconContainer { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(1f), - Radius = 10, + Size = new Vector2(20), + Margin = new MarginPadding { Horizontal = 5f }, + Colour = colourProvider.Background5, }, - Children = new Drawable[] + Children = new[] { - new BufferedContainer - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - backgroundBorder = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.ForStarDifficulty(0), - EdgeSmoothness = new Vector2(2, 0), - }, - rightContainer = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Masking = true, - CornerRadius = corner_radius, - RelativeSizeAxes = Axes.X, - Height = HEIGHT, - X = colour_box_width, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4), - }, - starRatingGradient = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - }, - }, - } - }, - iconContainer = new ConstrainedIconContainer - { - X = colour_box_width / 2, - Origin = Anchor.Centre, - Anchor = Anchor.CentreLeft, - Size = new Vector2(20), - Colour = colourProvider.Background5, - }, new FillFlowContainer { - Padding = new MarginPadding { Top = 8, Left = colour_box_width + corner_radius }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding { Left = 10f }, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, Children = new Drawable[] @@ -216,34 +160,10 @@ namespace osu.Game.Screens.SelectV2 } } }, - hoverLayer = new Box - { - Colour = colours.Blue.Opacity(0.1f), - Alpha = 0, - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - }, - activationFlash = new Box - { - Blending = BlendingParameters.Additive, - Alpha = 0f, - RelativeSizeAxes = Axes.Both, - }, - new HoverSounds(), } }; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) - { - var inputRectangle = panel.DrawRectangle; - - // Cover the gaps introduced by the spacing between BeatmapPanels. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -260,8 +180,8 @@ namespace osu.Game.Screens.SelectV2 updateKeyCount(); }, true); - Selected.BindValueChanged(_ => updateSelectionDisplay(), true); - KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + Selected.BindValueChanged(s => panel.Active.Value = s.NewValue, true); + KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); } protected override void PrepareForUse() @@ -271,63 +191,25 @@ namespace osu.Game.Screens.SelectV2 Debug.Assert(Item != null); var beatmap = (BeatmapInfo)Item.Model; - iconContainer.Icon = beatmap.Ruleset.CreateInstance().CreateIcon(); + difficultyIcon.Icon = beatmap.Ruleset.CreateInstance().CreateIcon(); difficultyRank.Beatmap = beatmap; difficultyText.Text = beatmap.DifficultyName; authorText.Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmap.Metadata.Author.Username); - starDifficultyBindable = null; - computeStarRating(); updateKeyCount(); - updateSelectionDisplay(); FinishTransforms(true); - this.FadeInFromZero(duration, Easing.OutQuint); - - // todo: only do this when visible. - // starCounter.ReplayAnimation(); } - private void updateSelectionDisplay() + protected override void FreeAfterUse() { - bool selected = Selected.Value; + base.FreeAfterUse(); - rightContainer.ResizeHeightTo(selected ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint); - - updatePanelPosition(); - updateEdgeEffectColour(); - } - - private void updateKeyboardSelectedDisplay() - { - updatePanelPosition(); - updateHover(); - } - - private void updatePanelPosition() - { - float x = difficulty_x_offset + selected_x_offset + preselected_x_offset; - - if (Selected.Value) - x -= selected_x_offset; - - if (KeyboardSelected.Value) - x -= preselected_x_offset; - - this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); - } - - private void updateHover() - { - bool hovered = IsHovered || KeyboardSelected.Value; - - if (hovered) - hoverLayer.FadeIn(100, Easing.OutQuint); - else - hoverLayer.FadeOut(1000, Easing.OutQuint); + difficultyRank.Beatmap = null; + starDifficultyBindable = null; } private void computeStarRating() @@ -341,34 +223,7 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token); - starDifficultyBindable.BindValueChanged(d => - { - var value = d.NewValue ?? default; - - starRatingDisplay.Current.Value = value; - starCounter.Current = (float)value.Stars; - - iconContainer.FadeColour(value.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); - - var starRatingColour = colours.ForStarDifficulty(value.Stars); - - backgroundBorder.FadeColour(starRatingColour, duration, Easing.OutQuint); - starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint); - starRatingGradient.FadeColour(ColourInfo.GradientHorizontal(starRatingColour.Opacity(0.25f), starRatingColour.Opacity(0)), duration, Easing.OutQuint); - starRatingGradient.FadeIn(duration, Easing.OutQuint); - - // todo: this doesn't work for dark star rating colours, still not sure how to fix. - activationFlash.FadeColour(starRatingColour, duration, Easing.OutQuint); - - updateEdgeEffectColour(); - }, true); - } - - private void updateEdgeEffectColour() - { - panel.FadeEdgeEffectTo(Selected.Value - ? colours.ForStarDifficulty(starDifficultyBindable?.Value?.Stars ?? 0f).Opacity(0.5f) - : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); + starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true); } private void updateKeyCount() @@ -392,16 +247,18 @@ namespace osu.Game.Screens.SelectV2 keyCountText.Alpha = 0; } - protected override bool OnHover(HoverEvent e) + private void updateDisplay() { - updateHover(); - return true; - } + var starDifficulty = starDifficultyBindable?.Value ?? default; - protected override void OnHoverLost(HoverLostEvent e) - { - updateHover(); - base.OnHoverLost(e); + starRatingDisplay.Current.Value = starDifficulty; + starCounter.Current = (float)starDifficulty.Stars; + + difficultyIcon.FadeColour(starDifficulty.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); + + var starRatingColour = colours.ForStarDifficulty(starDifficulty.Stars); + starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint); + panel.AccentColour = starRatingColour; } protected override bool OnClick(ClickEvent e) @@ -430,7 +287,7 @@ namespace osu.Game.Screens.SelectV2 public void Activated() { - activationFlash.FadeOutFromOne(500, Easing.OutQuint); + panel.Flash(); } #endregion diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index aabc39f27f..f5d7e0594b 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -6,12 +6,9 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; 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.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; @@ -19,10 +16,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { @@ -30,18 +25,22 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - private const float arrow_container_width = 20; - private const float corner_radius = 10; - // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. - private const float preselected_x_offset = 25f; - private const float expanded_x_offset = 50f; - private const float duration = 500; + private CarouselPanelPiece panel = null!; + private BeatmapSetPanelBackground background = null!; + + private OsuSpriteText titleText = null!; + private OsuSpriteText artistText = null!; + private Drawable chevronIcon = null!; + private UpdateBeatmapSetButtonV2 updateButton = null!; + private BeatmapSetOnlineStatusPill statusPill = null!; + private DifficultySpectrumDisplay difficultiesDisplay = null!; + [Resolved] private BeatmapCarousel? carousel { get; set; } @@ -51,22 +50,15 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private BeatmapManager beatmaps { get; set; } = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) + { + var inputRectangle = panel.TopLevelContent.DrawRectangle; - private Container panel = null!; - private Box backgroundBorder = null!; - private BeatmapSetPanelBackground background = null!; - private Container backgroundContainer = null!; - private FillFlowContainer mainFlowContainer = null!; - private SpriteIcon chevronIcon = null!; - private Box hoverLayer = null!; + // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - private OsuSpriteText titleText = null!; - private OsuSpriteText artistText = null!; - private UpdateBeatmapSetButtonV2 updateButton = null!; - private BeatmapSetOnlineStatusPill statusPill = null!; - private DifficultySpectrumDisplay difficultiesDisplay = null!; + return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); + } [BackgroundDependencyLoader] private void load() @@ -76,137 +68,89 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X; Height = HEIGHT; - InternalChild = panel = new Container + InternalChild = panel = new CarouselPanelPiece(set_x_offset) { - Masking = true, - CornerRadius = corner_radius, - RelativeSizeAxes = Axes.Both, - X = corner_radius, - EdgeEffect = new EdgeEffectParameters + Icon = chevronIcon = new Container { - Type = EdgeEffectType.Shadow, - Radius = 10, - }, - Children = new Drawable[] - { - new BufferedContainer + Size = new Vector2(22), + Child = new SpriteIcon { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - backgroundBorder = new Box - { - RelativeSizeAxes = Axes.Y, - Alpha = 0, - EdgeSmoothness = new Vector2(2, 0), - }, - backgroundContainer = new Container - { - Masking = true, - CornerRadius = corner_radius, - RelativeSizeAxes = Axes.X, - MaskingSmoothness = 2, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - background = new BeatmapSetPanelBackground - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }, - }, - }, - } - }, - chevronIcon = new SpriteIcon - { - X = arrow_container_width / 2, + Anchor = Anchor.Centre, Origin = Anchor.Centre, - Anchor = Anchor.CentreLeft, Icon = FontAwesome.Solid.ChevronRight, Size = new Vector2(12), + X = 1f, Colour = colourProvider.Background5, }, - mainFlowContainer = new FillFlowContainer + }, + Background = background = new BeatmapSetPanelBackground + { + RelativeSizeAxes = Axes.Both, + }, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, - Children = new Drawable[] + titleText = new OsuSpriteText { - titleText = new OsuSpriteText + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + }, + artistText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5f }, + Children = new Drawable[] { - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), - Shadow = true, - }, - artistText = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), - Shadow = true, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5f }, - Children = new Drawable[] + updateButton = new UpdateBeatmapSetButtonV2 { - updateButton = new UpdateBeatmapSetButtonV2 - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5f, Top = -2f }, - }, - statusPill = new BeatmapSetOnlineStatusPill - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Margin = new MarginPadding { Right = 5f }, - }, - difficultiesDisplay = new DifficultySpectrumDisplay - { - DotSize = new Vector2(5, 10), - DotSpacing = 2, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f, Top = -2f }, }, - } + statusPill = new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Margin = new MarginPadding { Right = 5f }, + }, + difficultiesDisplay = new DifficultySpectrumDisplay + { + DotSize = new Vector2(5, 10), + DotSpacing = 2, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }, } - }, - hoverLayer = new Box - { - Colour = colours.Blue.Opacity(0.1f), - Alpha = 0, - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - }, - new HoverSounds(), + } } }; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) - { - var inputRectangle = panel.DrawRectangle; - - // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); - } - protected override void LoadComplete() { base.LoadComplete(); - Expanded.BindValueChanged(_ => updateExpandedDisplay(), true); - KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + Expanded.BindValueChanged(_ => onExpanded(), true); + KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); + } + + private void onExpanded() + { + panel.Active.Value = Expanded.Value; + chevronIcon.ResizeWidthTo(Expanded.Value ? 22 : 0f, duration, Easing.OutQuint); + chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } protected override void PrepareForUse() @@ -226,9 +170,7 @@ namespace osu.Game.Screens.SelectV2 statusPill.Status = beatmapSet.Status; difficultiesDisplay.BeatmapSet = beatmapSet; - updateExpandedDisplay(); FinishTransforms(true); - this.FadeInFromZero(duration, Easing.OutQuint); } @@ -241,70 +183,6 @@ namespace osu.Game.Screens.SelectV2 difficultiesDisplay.BeatmapSet = null; } - private void updateExpandedDisplay() - { - if (Item == null) - return; - - updatePanelPosition(); - - backgroundBorder.RelativeSizeAxes = Expanded.Value ? Axes.Both : Axes.Y; - backgroundBorder.Width = Expanded.Value ? 1 : arrow_container_width + corner_radius; - backgroundBorder.FadeTo(Expanded.Value ? 1 : 0, duration, Easing.OutQuint); - chevronIcon.FadeTo(Expanded.Value ? 1 : 0, duration, Easing.OutQuint); - - backgroundContainer.ResizeHeightTo(Expanded.Value ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint); - backgroundContainer.MoveToX(Expanded.Value ? arrow_container_width : 0, duration, Easing.OutQuint); - mainFlowContainer.MoveToX(Expanded.Value ? arrow_container_width : 0, duration, Easing.OutQuint); - - panel.EdgeEffect = panel.EdgeEffect with { Radius = Expanded.Value ? 15 : 10 }; - - panel.FadeEdgeEffectTo(Expanded.Value - ? Color4Extensions.FromHex(@"4EBFFF").Opacity(0.5f) - : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); - } - - private void updateKeyboardSelectedDisplay() - { - updatePanelPosition(); - updateHover(); - } - - private void updatePanelPosition() - { - float x = set_x_offset + expanded_x_offset + preselected_x_offset; - - if (Expanded.Value) - x -= expanded_x_offset; - - if (KeyboardSelected.Value) - x -= preselected_x_offset; - - this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); - } - - private void updateHover() - { - bool hovered = IsHovered || KeyboardSelected.Value; - - if (hovered) - hoverLayer.FadeIn(100, Easing.OutQuint); - else - hoverLayer.FadeOut(1000, Easing.OutQuint); - } - - protected override bool OnHover(HoverEvent e) - { - updateHover(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateHover(); - base.OnHoverLost(e); - } - protected override bool OnClick(ClickEvent e) { if (carousel != null) diff --git a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs index c0a5f828f4..a8fa2224d7 100644 --- a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs @@ -8,12 +8,9 @@ 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.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -21,13 +18,11 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { @@ -35,15 +30,9 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - private const float difficulty_icon_container_width = 30; - private const float corner_radius = 10; - // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). - private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. - - private const float preselected_x_offset = 25f; - private const float selected_x_offset = 50f; + private const float standalone_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. private const float duration = 500; @@ -71,12 +60,8 @@ namespace osu.Game.Screens.SelectV2 private IBindable? starDifficultyBindable; private CancellationTokenSource? starDifficultyCancellationSource; - private Container panel = null!; - private Box backgroundBorder = null!; + private CarouselPanelPiece panel = null!; private BeatmapSetPanelBackground background = null!; - private Container backgroundContainer = null!; - private FillFlowContainer mainFlowContainer = null!; - private Box hoverLayer = null!; private OsuSpriteText titleText = null!; private OsuSpriteText artistText = null!; @@ -91,6 +76,16 @@ namespace osu.Game.Screens.SelectV2 private OsuSpriteText difficultyName = null!; private OsuSpriteText difficultyAuthor = null!; + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) + { + var inputRectangle = panel.TopLevelContent.DrawRectangle; + + // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + + return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); + } + [BackgroundDependencyLoader] private void load() { @@ -100,167 +95,109 @@ namespace osu.Game.Screens.SelectV2 Width = 1f; Height = HEIGHT; - InternalChild = panel = new Container + InternalChild = panel = new CarouselPanelPiece(standalone_x_offset) { - Masking = true, - CornerRadius = corner_radius, - RelativeSizeAxes = Axes.Both, - X = corner_radius, - EdgeEffect = new EdgeEffectParameters + Icon = difficultyIcon = new ConstrainedIconContainer { - Type = EdgeEffectType.Shadow, - Radius = 10, + Size = new Vector2(20), + Margin = new MarginPadding { Horizontal = 5f }, + Colour = colourProvider.Background5, }, - Children = new Drawable[] + Background = background = new BeatmapSetPanelBackground { - new BufferedContainer + RelativeSizeAxes = Axes.Both, + }, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + titleText = new OsuSpriteText { - backgroundBorder = new Box + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + artistText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5f }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Y, - Alpha = 0, - EdgeSmoothness = new Vector2(2, 0), - }, - backgroundContainer = new Container - { - Masking = true, - CornerRadius = corner_radius, - RelativeSizeAxes = Axes.X, - MaskingSmoothness = 2, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] + updateButton = new UpdateBeatmapSetButtonV2 { - background = new BeatmapSetPanelBackground - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f, Top = -2f }, }, - }, - } - }, - difficultyIcon = new ConstrainedIconContainer - { - X = difficulty_icon_container_width / 2, - Origin = Anchor.Centre, - Anchor = Anchor.CentreLeft, - Size = new Vector2(20), - }, - mainFlowContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, - Children = new Drawable[] - { - titleText = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), - Shadow = true, - }, - artistText = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), - Shadow = true, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5f }, - Children = new Drawable[] + statusPill = new BeatmapSetOnlineStatusPill { - updateButton = new UpdateBeatmapSetButtonV2 + AutoSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Margin = new MarginPadding { Right = 5f }, + }, + difficultyLine = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5f, Top = -2f }, - }, - statusPill = new BeatmapSetOnlineStatusPill - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Margin = new MarginPadding { Right = 5f }, - }, - difficultyLine = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small) { - difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small) - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Scale = new Vector2(8f / 9f), - Margin = new MarginPadding { Right = 5f }, - }, - difficultyRank = new TopLocalRankV2 - { - Scale = new Vector2(8f / 11), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5f }, - }, - difficultyKeyCountText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Alpha = 0, - Margin = new MarginPadding { Bottom = 2f }, - }, - difficultyName = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Margin = new MarginPadding { Right = 5f, Bottom = 2f }, - }, - difficultyAuthor = new OsuSpriteText - { - Colour = colourProvider.Content2, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Margin = new MarginPadding { Right = 5f, Bottom = 2f }, - } + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Scale = new Vector2(8f / 9f), + Margin = new MarginPadding { Right = 5f }, + }, + difficultyRank = new TopLocalRankV2 + { + Scale = new Vector2(8f / 11), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f }, + }, + difficultyKeyCountText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + Margin = new MarginPadding { Bottom = 2f }, + }, + difficultyName = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 5f, Bottom = 2f }, + }, + difficultyAuthor = new OsuSpriteText + { + Colour = colourProvider.Content2, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 5f, Bottom = 2f }, } - }, + } }, - } + }, } - }, - hoverLayer = new Box - { - Colour = colours.Blue.Opacity(0.1f), - Alpha = 0, - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - }, - new HoverSounds(), - } + } + }, }; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) - { - var inputRectangle = panel.DrawRectangle; - - // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -277,8 +214,8 @@ namespace osu.Game.Screens.SelectV2 updateKeyCount(); }, true); - Selected.BindValueChanged(_ => updateSelectedDisplay(), true); - KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + Selected.BindValueChanged(s => panel.Active.Value = s.NewValue, true); + KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); } protected override void PrepareForUse() @@ -308,7 +245,6 @@ namespace osu.Game.Screens.SelectV2 computeStarRating(); - updateSelectedDisplay(); FinishTransforms(true); this.FadeInFromZero(duration, Easing.OutQuint); @@ -324,55 +260,6 @@ namespace osu.Game.Screens.SelectV2 starDifficultyBindable = null; } - private void updateSelectedDisplay() - { - if (Item == null) - return; - - updatePanelPosition(); - - backgroundBorder.RelativeSizeAxes = Selected.Value ? Axes.Both : Axes.Y; - backgroundBorder.Width = Selected.Value ? 1 : difficulty_icon_container_width + corner_radius; - backgroundBorder.FadeTo(Selected.Value ? 1 : 0, duration, Easing.OutQuint); - difficultyIcon.FadeTo(Selected.Value ? 1 : 0, duration, Easing.OutQuint); - - backgroundContainer.ResizeHeightTo(Selected.Value ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint); - backgroundContainer.MoveToX(Selected.Value ? difficulty_icon_container_width : 0, duration, Easing.OutQuint); - mainFlowContainer.MoveToX(Selected.Value ? difficulty_icon_container_width : 0, duration, Easing.OutQuint); - - panel.EdgeEffect = panel.EdgeEffect with { Radius = Selected.Value ? 15 : 10 }; - updateEdgeEffectColour(); - } - - private void updateKeyboardSelectedDisplay() - { - updatePanelPosition(); - updateHover(); - } - - private void updatePanelPosition() - { - float x = set_x_offset + selected_x_offset + preselected_x_offset; - - if (Selected.Value) - x -= selected_x_offset; - - if (KeyboardSelected.Value) - x -= preselected_x_offset; - - this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); - } - - private void updateHover() - { - bool hovered = IsHovered || KeyboardSelected.Value; - - if (hovered) - hoverLayer.FadeIn(100, Easing.OutQuint); - else - hoverLayer.FadeOut(1000, Easing.OutQuint); - } - private void computeStarRating() { starDifficultyCancellationSource?.Cancel(); @@ -384,23 +271,7 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token); - starDifficultyBindable.BindValueChanged(d => - { - var value = d.NewValue ?? default; - - backgroundBorder.FadeColour(colours.ForStarDifficulty(value.Stars), duration, Easing.OutQuint); - difficultyIcon.FadeColour(value.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); - difficultyStarRating.Current.Value = value; - - updateEdgeEffectColour(); - }, true); - } - - private void updateEdgeEffectColour() - { - panel.FadeEdgeEffectTo(Selected.Value - ? colours.ForStarDifficulty(starDifficultyBindable?.Value?.Stars ?? 0f).Opacity(0.5f) - : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); + starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true); } private void updateKeyCount() @@ -424,16 +295,13 @@ namespace osu.Game.Screens.SelectV2 difficultyKeyCountText.Alpha = 0; } - protected override bool OnHover(HoverEvent e) + private void updateDisplay() { - updateHover(); - return true; - } + var starDifficulty = starDifficultyBindable?.Value ?? default; - protected override void OnHoverLost(HoverLostEvent e) - { - updateHover(); - base.OnHoverLost(e); + panel.AccentColour = colours.ForStarDifficulty(starDifficulty.Stars); + difficultyIcon.FadeColour(starDifficulty.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); + difficultyStarRating.Current.Value = starDifficulty; } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs b/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs new file mode 100644 index 0000000000..a7f2b3a163 --- /dev/null +++ b/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs @@ -0,0 +1,240 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class CarouselPanelPiece : Container + { + private const float corner_radius = 10; + + private const float left_edge_x_offset = 20f; + private const float keyboard_active_x_offset = 25f; + private const float active_x_offset = 50f; + + private const float duration = 500; + + private readonly float panelXOffset; + + private readonly Box backgroundBorder; + private readonly Box backgroundGradient; + private readonly Box backgroundAccentGradient; + private readonly Container backgroundLayer; + private readonly Container backgroundLayerHorizontalPadding; + private readonly Container backgroundContainer; + private readonly Container iconContainer; + private readonly Box activationFlash; + private readonly Box hoverLayer; + + public Container TopLevelContent { get; } + + protected override Container Content { get; } + + public Drawable Background + { + set => backgroundContainer.Child = value; + } + + public Drawable Icon + { + set => iconContainer.Child = value; + } + + private Color4? accentColour; + + public Color4? AccentColour + { + get => accentColour; + set + { + accentColour = value; + updateDisplay(); + } + } + + public readonly BindableBool Active = new BindableBool(); + public readonly BindableBool KeyboardActive = new BindableBool(); + + public CarouselPanelPiece(float panelXOffset) + { + this.panelXOffset = panelXOffset; + + RelativeSizeAxes = Axes.Both; + + InternalChild = TopLevelContent = new Container + { + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + X = corner_radius, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(1f), + Radius = 10, + }, + Children = new Drawable[] + { + new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundBorder = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + backgroundLayerHorizontalPadding = new Container + { + RelativeSizeAxes = Axes.Both, + Child = backgroundLayer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundGradient = new Box + { + RelativeSizeAxes = Axes.Both, + }, + backgroundAccentGradient = new Box + { + RelativeSizeAxes = Axes.Both, + }, + backgroundContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + } + }, + }, + } + }, + }, + iconContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + Content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = panelXOffset + corner_radius }, + }, + hoverLayer = new Box + { + Alpha = 0, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }, + activationFlash = new Box + { + Colour = Color4.White.Opacity(0.4f), + Blending = BlendingParameters.Additive, + Alpha = 0f, + RelativeSizeAxes = Axes.Both, + }, + new HoverSounds(), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) + { + hoverLayer.Colour = colours.Blue.Opacity(0.1f); + backgroundGradient.Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Active.BindValueChanged(_ => updateDisplay()); + KeyboardActive.BindValueChanged(_ => updateDisplay(), true); + } + + public void Flash() + { + activationFlash.FadeOutFromOne(500, Easing.OutQuint); + } + + private void updateDisplay() + { + backgroundLayer.TransformTo(nameof(Padding), backgroundLayer.Padding with { Vertical = Active.Value ? 2f : 0f }, duration, Easing.OutQuint); + + var backgroundColour = accentColour ?? Color4.White; + var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF"); + + backgroundAccentGradient.FadeColour(ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)), duration, Easing.OutQuint); + backgroundBorder.FadeColour(backgroundColour, duration, Easing.OutQuint); + + TopLevelContent.FadeEdgeEffectTo(Active.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); + + updateXOffset(); + updateHover(); + } + + private void updateXOffset() + { + float x = panelXOffset + active_x_offset + keyboard_active_x_offset + left_edge_x_offset; + + if (Active.Value) + x -= active_x_offset; + + if (KeyboardActive.Value) + x -= keyboard_active_x_offset; + + this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); + } + + private void updateHover() + { + bool hovered = IsHovered || KeyboardActive.Value; + + if (hovered) + hoverLayer.FadeIn(100, Easing.OutQuint); + else + hoverLayer.FadeOut(1000, Easing.OutQuint); + } + + protected override bool OnHover(HoverEvent e) + { + updateDisplay(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateDisplay(); + base.OnHoverLost(e); + } + + protected override void Update() + { + base.Update(); + Content.Padding = Content.Padding with { Left = iconContainer.DrawWidth }; + backgroundLayerHorizontalPadding.Padding = new MarginPadding { Left = iconContainer.DrawWidth }; + } + } +} diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index b5fa338f82..12c4df830c 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -10,10 +10,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -24,137 +24,83 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - private const float corner_radius = 10; - - private const float glow_offset = 10f; // extra space for any edge effect to not be cutoff by the right edge of the carousel. - private const float preselected_x_offset = 25f; - private const float selected_x_offset = 50f; - private const float duration = 500; [Resolved] private BeatmapCarousel? carousel { get; set; } - private Container panel = null!; - private Box activationFlash = null!; + private CarouselPanelPiece panel = null!; + private Drawable chevronIcon = null!; private OsuSpriteText titleText = null!; - private Box hoverLayer = null!; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) { - var inputRectangle = panel.DrawRectangle; + var inputRectangle = panel.TopLevelContent.DrawRectangle; - // Cover a gap introduced by the spacing between a GroupPanel and a BeatmapPanel either below/above it. + // Cover a gap introduced by the spacing between a GroupPanel and other panel types either below/above it. inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos)); + return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider) { Anchor = Anchor.TopRight; Origin = Anchor.TopRight; RelativeSizeAxes = Axes.X; Height = HEIGHT; - InternalChild = panel = new Container + InternalChild = panel = new CarouselPanelPiece(0) { - RelativeSizeAxes = Axes.Both, - CornerRadius = corner_radius, - Masking = true, - X = corner_radius, + Icon = chevronIcon = new SpriteIcon + { + AlwaysPresent = true, + Icon = FontAwesome.Solid.ChevronDown, + Size = new Vector2(12), + Margin = new MarginPadding { Horizontal = 5f }, + X = 2f, + Colour = colourProvider.Background3, + }, + Background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Dark1, + }, + AccentColour = colourProvider.Highlight1, Children = new Drawable[] { - new Container + titleText = new OsuSpriteText { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10f }, - Child = new Container + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + X = 10f, + }, + new CircularContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 20f }, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = corner_radius, - Masking = true, - Children = new Drawable[] + new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background6, - }, - } - } - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10f }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - CornerRadius = corner_radius, - Masking = true, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + new OsuSpriteText { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, - }, - titleText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - X = 10f, - }, - new CircularContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(50f, 14f), - Margin = new MarginPadding { Right = 30f }, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), - // TODO: requires Carousel/CarouselItem-side implementation - Text = "43", - UseFullGlyphHeight = false, - } - }, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), + // TODO: requires Carousel/CarouselItem-side implementation + Text = "43", + UseFullGlyphHeight = false, } - } - }, - activationFlash = new Box - { - Colour = Color4.White, - Blending = BlendingParameters.Additive, - Alpha = 0, - RelativeSizeAxes = Axes.Both, - }, - hoverLayer = new Box - { - Colour = colours.Blue.Opacity(0.1f), - Alpha = 0, - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - }, - new HoverSounds(), + }, + } } }; } @@ -163,17 +109,17 @@ namespace osu.Game.Screens.SelectV2 { base.LoadComplete(); - Expanded.BindValueChanged(_ => updateExpandedDisplay(), true); - KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + Expanded.BindValueChanged(_ => onExpanded(), true); + KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); } - private void updateExpandedDisplay() + private void onExpanded() { - updatePanelPosition(); + panel.Active.Value = Expanded.Value; + panel.Flash(); - // todo: figma shares no extra visual feedback on this. - - activationFlash.FadeTo(0.2f).FadeTo(0f, 500, Easing.OutQuint); + chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); + chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } protected override void PrepareForUse() @@ -186,6 +132,7 @@ namespace osu.Game.Screens.SelectV2 titleText.Text = group.Title; + FinishTransforms(true); this.FadeInFromZero(500, Easing.OutQuint); } @@ -197,47 +144,6 @@ namespace osu.Game.Screens.SelectV2 return true; } - private void updateKeyboardSelectedDisplay() - { - updatePanelPosition(); - updateHover(); - } - - private void updatePanelPosition() - { - float x = glow_offset + selected_x_offset + preselected_x_offset; - - if (Expanded.Value) - x -= selected_x_offset; - - if (KeyboardSelected.Value) - x -= preselected_x_offset; - - this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); - } - - private void updateHover() - { - bool hovered = IsHovered || KeyboardSelected.Value; - - if (hovered) - hoverLayer.FadeIn(100, Easing.OutQuint); - else - hoverLayer.FadeOut(1000, Easing.OutQuint); - } - - protected override bool OnHover(HoverEvent e) - { - updateHover(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateHover(); - base.OnHoverLost(e); - } - #region ICarouselPanel public CarouselItem? Item { get; set; } @@ -249,7 +155,7 @@ namespace osu.Game.Screens.SelectV2 public void Activated() { - // sets should never be activated. + // groups should never be activated. throw new InvalidOperationException(); } diff --git a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs index 76e3da2500..8e179ec5c1 100644 --- a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs +++ b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -26,10 +27,6 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel. - private const float preselected_x_offset = 25f; - private const float expanded_x_offset = 50f; - private const float duration = 500; [Resolved] @@ -41,20 +38,20 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - private Box activationFlash = null!; - private Box outerLayer = null!; + private CarouselPanelPiece panel = null!; + private Drawable chevronIcon = null!; + private Box contentBackground = null!; private StarRatingDisplay starRatingDisplay = null!; private StarCounter starCounter = null!; - private Box hoverLayer = null!; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) { - var inputRectangle = DrawRectangle; + var inputRectangle = panel.TopLevelContent.DrawRectangle; - // Cover a gap introduced by the spacing between a GroupPanel and a BeatmapPanel either below/above it. + // Cover a gap introduced by the spacing between a GroupPanel and other panel types either below/above it. inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); + return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); } [BackgroundDependencyLoader] @@ -65,118 +62,71 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X; Height = HEIGHT; - InternalChild = new Container + InternalChild = panel = new CarouselPanelPiece(0) { - RelativeSizeAxes = Axes.Both, - CornerRadius = 10f, - Masking = true, + Icon = chevronIcon = new SpriteIcon + { + AlwaysPresent = true, + Icon = FontAwesome.Solid.ChevronDown, + Size = new Vector2(12), + Margin = new MarginPadding { Horizontal = 5f }, + X = 2f, + }, + Background = contentBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Dark1, + }, + AccentColour = colourProvider.Highlight1, Children = new Drawable[] { - new Container + new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10f }, - Child = new Container + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f, 0f), + Margin = new MarginPadding { Left = 10f }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 10f, - Masking = true, - Children = new Drawable[] + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background6, - }, - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(8f / 20f), + }, } }, - outerLayer = new Box + new CircularContainer { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10f }, - Child = new Container + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 20f }, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 10f, - Masking = true, - Children = new Drawable[] + new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.2f), - }, - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10f, 0f), - Margin = new MarginPadding { Left = 10f }, - Children = new Drawable[] - { - starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - starCounter = new StarCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(8f / 20f), - }, - } - }, - new CircularContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(50f, 14f), - Margin = new MarginPadding { Right = 30f }, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), - // TODO: requires Carousel/CarouselItem-side implementation - Text = "43", - UseFullGlyphHeight = false, - } - }, - }, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), + // TODO: requires Carousel/CarouselItem-side implementation + Text = "43", + UseFullGlyphHeight = false, } - } - }, - activationFlash = new Box - { - Colour = Color4.White, - Blending = BlendingParameters.Additive, - Alpha = 0, - RelativeSizeAxes = Axes.Both, - }, - hoverLayer = new Box - { - Colour = colours.Blue.Opacity(0.1f), - Alpha = 0, - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - }, - new HoverSounds(), + }, + } } }; } @@ -185,17 +135,17 @@ namespace osu.Game.Screens.SelectV2 { base.LoadComplete(); - Expanded.BindValueChanged(_ => updateExpandedDisplay(), true); - KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true); + Expanded.BindValueChanged(_ => onExpanded(), true); + KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); } - private void updateExpandedDisplay() + private void onExpanded() { - updatePanelPosition(); + panel.Active.Value = Expanded.Value; + panel.Flash(); - // todo: figma shares no extra visual feedback on this. - - activationFlash.FadeTo(0.2f).FadeTo(0f, 500, Easing.OutQuint); + chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); + chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } protected override void PrepareForUse() @@ -209,12 +159,15 @@ namespace osu.Game.Screens.SelectV2 Color4 colour = group.StarNumber >= 9 ? OsuColour.Gray(0.2f) : colours.ForStarDifficulty(group.StarNumber); Color4 contentColour = group.StarNumber >= 7 ? colours.Orange1 : colourProvider.Background5; - outerLayer.Colour = colour; - starCounter.Colour = contentColour; + panel.AccentColour = colour; + contentBackground.Colour = colour.Darken(0.3f); starRatingDisplay.Current.Value = new StarDifficulty(group.StarNumber, 0); starCounter.Current = group.StarNumber; + chevronIcon.Colour = contentColour; + starCounter.Colour = contentColour; + this.FadeInFromZero(500, Easing.OutQuint); } @@ -226,47 +179,6 @@ namespace osu.Game.Screens.SelectV2 return true; } - private void updateKeyboardSelectedDisplay() - { - updatePanelPosition(); - updateHover(); - } - - private void updatePanelPosition() - { - float x = glow_offset + expanded_x_offset + preselected_x_offset; - - if (Expanded.Value) - x -= expanded_x_offset; - - if (KeyboardSelected.Value) - x -= preselected_x_offset; - - this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); - } - - private void updateHover() - { - bool hovered = IsHovered || KeyboardSelected.Value; - - if (hovered) - hoverLayer.FadeIn(100, Easing.OutQuint); - else - hoverLayer.FadeOut(1000, Easing.OutQuint); - } - - protected override bool OnHover(HoverEvent e) - { - updateHover(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateHover(); - base.OnHoverLost(e); - } - #region ICarouselPanel public CarouselItem? Item { get; set; } From 3ab208bb4643e6bd0512bd5b274d958cbef3a8fc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 6 Feb 2025 02:21:44 -0500 Subject: [PATCH 23/92] Fix group visual test scene --- .../SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs index eea3870117..d9f4a1630f 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs @@ -41,13 +41,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2 new GroupPanel { Item = new CarouselItem(new GroupDefinition("Group A")), - Selected = { Value = true } + Expanded = { Value = true } }, new GroupPanel { Item = new CarouselItem(new GroupDefinition("Group A")), KeyboardSelected = { Value = true }, - Selected = { Value = true } + Expanded = { Value = true } }, new StarsGroupPanel { @@ -56,6 +56,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 new StarsGroupPanel { Item = new CarouselItem(new StarsGroupDefinition(3)), + Expanded = { Value = true } }, new StarsGroupPanel { @@ -64,6 +65,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 new StarsGroupPanel { Item = new CarouselItem(new StarsGroupDefinition(7)), + Expanded = { Value = true } }, new StarsGroupPanel { @@ -72,6 +74,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 new StarsGroupPanel { Item = new CarouselItem(new StarsGroupDefinition(9)), + Expanded = { Value = true } }, } }; From e1d6ce5ff44569c0b911540ebabbf116319b0eab Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 6 Feb 2025 02:25:12 -0500 Subject: [PATCH 24/92] Add V2 suffix for easier test browsing --- ...yPanel.cs => TestSceneBeatmapCarouselV2DifficultyPanel.cs} | 4 ++-- ...lGroupPanel.cs => TestSceneBeatmapCarouselV2GroupPanel.cs} | 4 ++-- ...ouselSetPanel.cs => TestSceneBeatmapCarouselV2SetPanel.cs} | 4 ++-- ...ePanel.cs => TestSceneBeatmapCarouselV2StandalonePanel.cs} | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneBeatmapCarouselDifficultyPanel.cs => TestSceneBeatmapCarouselV2DifficultyPanel.cs} (95%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneBeatmapCarouselGroupPanel.cs => TestSceneBeatmapCarouselV2GroupPanel.cs} (95%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneBeatmapCarouselSetPanel.cs => TestSceneBeatmapCarouselV2SetPanel.cs} (95%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneBeatmapCarouselStandalonePanel.cs => TestSceneBeatmapCarouselV2StandalonePanel.cs} (95%) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs similarity index 95% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs index a9f73759f7..93472e7b81 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselDifficultyPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs @@ -18,14 +18,14 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneBeatmapCarouselDifficultyPanel : ThemeComparisonTestScene + public partial class TestSceneBeatmapCarouselV2DifficultyPanel : ThemeComparisonTestScene { [Resolved] private BeatmapManager beatmaps { get; set; } = null!; private BeatmapInfo beatmap = null!; - public TestSceneBeatmapCarouselDifficultyPanel() + public TestSceneBeatmapCarouselV2DifficultyPanel() : base(false) { } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs similarity index 95% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs index d9f4a1630f..9808e41f73 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselGroupPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs @@ -9,9 +9,9 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneBeatmapCarouselGroupPanel : ThemeComparisonTestScene + public partial class TestSceneBeatmapCarouselV2GroupPanel : ThemeComparisonTestScene { - public TestSceneBeatmapCarouselGroupPanel() + public TestSceneBeatmapCarouselV2GroupPanel() : base(false) { } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs similarity index 95% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs index 8f7cac2b58..540eae3be0 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs @@ -16,14 +16,14 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneBeatmapCarouselSetPanel : ThemeComparisonTestScene + public partial class TestSceneBeatmapCarouselV2SetPanel : ThemeComparisonTestScene { [Resolved] private BeatmapManager beatmaps { get; set; } = null!; private BeatmapSetInfo beatmapSet = null!; - public TestSceneBeatmapCarouselSetPanel() + public TestSceneBeatmapCarouselV2SetPanel() : base(false) { } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs similarity index 95% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs index a34ac31d5d..72f7a9e98c 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselStandalonePanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs @@ -18,14 +18,14 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneBeatmapCarouselStandalonePanel : ThemeComparisonTestScene + public partial class TestSceneBeatmapCarouselV2StandalonePanel : ThemeComparisonTestScene { [Resolved] private BeatmapManager beatmaps { get; set; } = null!; private BeatmapInfo beatmap = null!; - public TestSceneBeatmapCarouselStandalonePanel() + public TestSceneBeatmapCarouselV2StandalonePanel() : base(false) { } From 78cd093a47f70403428eb40f020c8a8beffc522e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 6 Feb 2025 02:44:40 -0500 Subject: [PATCH 25/92] Fix broken input handling with structural changes --- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 24 ++++--------------- osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 16 ++----------- .../SelectV2/BeatmapStandalonePanel.cs | 23 +++++++----------- .../Screens/SelectV2/CarouselPanelPiece.cs | 21 +++++++++++++++- osu.Game/Screens/SelectV2/GroupPanel.cs | 16 ++----------- osu.Game/Screens/SelectV2/StarsGroupPanel.cs | 16 ++----------- 6 files changed, 39 insertions(+), 77 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index bd4cf6d7cf..a878f966b8 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -64,16 +63,6 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private IBindable> mods { get; set; } = null!; - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) - { - var inputRectangle = panel.TopLevelContent.DrawRectangle; - - // Cover the gaps introduced by the spacing between BeatmapPanels. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); - } - [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -86,6 +75,7 @@ namespace osu.Game.Screens.SelectV2 InternalChild = panel = new CarouselPanelPiece(difficulty_x_offset) { + Action = onAction, Icon = difficultyIcon = new ConstrainedIconContainer { Size = new Vector2(20), @@ -261,19 +251,15 @@ namespace osu.Game.Screens.SelectV2 panel.AccentColour = starRatingColour; } - protected override bool OnClick(ClickEvent e) + private void onAction() { if (carousel == null) - return true; + return; if (carousel.CurrentSelection != Item!.Model) - { carousel.CurrentSelection = Item!.Model; - return true; - } - - carousel.TryActivateSelection(); - return true; + else + carousel.TryActivateSelection(); } #region ICarouselPanel diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index f5d7e0594b..951e76e0bc 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -50,16 +49,6 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private BeatmapManager beatmaps { get; set; } = null!; - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) - { - var inputRectangle = panel.TopLevelContent.DrawRectangle; - - // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); - } - [BackgroundDependencyLoader] private void load() { @@ -70,6 +59,7 @@ namespace osu.Game.Screens.SelectV2 InternalChild = panel = new CarouselPanelPiece(set_x_offset) { + Action = onAction, Icon = chevronIcon = new Container { Size = new Vector2(22), @@ -183,12 +173,10 @@ namespace osu.Game.Screens.SelectV2 difficultiesDisplay.BeatmapSet = null; } - protected override bool OnClick(ClickEvent e) + private void onAction() { if (carousel != null) carousel.CurrentSelection = Item!.Model; - - return true; } #region ICarouselPanel diff --git a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs index a8fa2224d7..8e201ec5bc 100644 --- a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs @@ -11,7 +11,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -76,16 +75,6 @@ namespace osu.Game.Screens.SelectV2 private OsuSpriteText difficultyName = null!; private OsuSpriteText difficultyAuthor = null!; - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) - { - var inputRectangle = panel.TopLevelContent.DrawRectangle; - - // Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); - } - [BackgroundDependencyLoader] private void load() { @@ -97,6 +86,7 @@ namespace osu.Game.Screens.SelectV2 InternalChild = panel = new CarouselPanelPiece(standalone_x_offset) { + Action = onAction, Icon = difficultyIcon = new ConstrainedIconContainer { Size = new Vector2(20), @@ -304,12 +294,15 @@ namespace osu.Game.Screens.SelectV2 difficultyStarRating.Current.Value = starDifficulty; } - protected override bool OnClick(ClickEvent e) + private void onAction() { - if (carousel != null) - carousel.CurrentSelection = Item!.Model; + if (carousel == null) + return; - return true; + if (carousel.CurrentSelection != Item!.Model) + carousel.CurrentSelection = Item!.Model; + else + carousel.TryActivateSelection(); } #region ICarouselPanel diff --git a/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs b/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs index a7f2b3a163..4b533e362a 100644 --- a/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs +++ b/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -69,6 +70,18 @@ namespace osu.Game.Screens.SelectV2 public readonly BindableBool Active = new BindableBool(); public readonly BindableBool KeyboardActive = new BindableBool(); + public Action? Action; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = TopLevelContent.DrawRectangle; + + // Cover potential gaps introduced by the spacing between panels. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + + return inputRectangle.Contains(TopLevelContent.ToLocalSpace(screenSpacePos)); + } + public CarouselPanelPiece(float panelXOffset) { this.panelXOffset = panelXOffset; @@ -221,7 +234,7 @@ namespace osu.Game.Screens.SelectV2 protected override bool OnHover(HoverEvent e) { updateDisplay(); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) @@ -230,6 +243,12 @@ namespace osu.Game.Screens.SelectV2 base.OnHoverLost(e); } + protected override bool OnClick(ClickEvent e) + { + Action?.Invoke(); + return true; + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index 12c4df830c..a757293e57 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -33,16 +32,6 @@ namespace osu.Game.Screens.SelectV2 private Drawable chevronIcon = null!; private OsuSpriteText titleText = null!; - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) - { - var inputRectangle = panel.TopLevelContent.DrawRectangle; - - // Cover a gap introduced by the spacing between a GroupPanel and other panel types either below/above it. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); - } - [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -53,6 +42,7 @@ namespace osu.Game.Screens.SelectV2 InternalChild = panel = new CarouselPanelPiece(0) { + Action = onAction, Icon = chevronIcon = new SpriteIcon { AlwaysPresent = true, @@ -136,12 +126,10 @@ namespace osu.Game.Screens.SelectV2 this.FadeInFromZero(500, Easing.OutQuint); } - protected override bool OnClick(ClickEvent e) + private void onAction() { if (carousel != null) carousel.CurrentSelection = Item!.Model; - - return true; } #region ICarouselPanel diff --git a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs index 8e179ec5c1..d345f9687e 100644 --- a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs +++ b/osu.Game/Screens/SelectV2/StarsGroupPanel.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -44,16 +43,6 @@ namespace osu.Game.Screens.SelectV2 private StarRatingDisplay starRatingDisplay = null!; private StarCounter starCounter = null!; - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) - { - var inputRectangle = panel.TopLevelContent.DrawRectangle; - - // Cover a gap introduced by the spacing between a GroupPanel and other panel types either below/above it. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos)); - } - [BackgroundDependencyLoader] private void load() { @@ -64,6 +53,7 @@ namespace osu.Game.Screens.SelectV2 InternalChild = panel = new CarouselPanelPiece(0) { + Action = onAction, Icon = chevronIcon = new SpriteIcon { AlwaysPresent = true, @@ -171,12 +161,10 @@ namespace osu.Game.Screens.SelectV2 this.FadeInFromZero(500, Easing.OutQuint); } - protected override bool OnClick(ClickEvent e) + private void onAction() { if (carousel != null) carousel.CurrentSelection = Item!.Model; - - return true; } #region ICarouselPanel From aa9727c020051e38d3ffc45f462e8f062ff11752 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 6 Feb 2025 02:44:52 -0500 Subject: [PATCH 26/92] Fix helper method in carousel test scene --- osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs index a3f6eaf152..9f7b4468dc 100644 --- a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs +++ b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs @@ -185,6 +185,7 @@ namespace osu.Game.Tests.Visual.SongSelect .Where(p => ((ICarouselPanel)p).Item?.IsVisible == true) .OrderBy(p => p.Y) .ElementAt(index) + .ChildrenOfType().Single() .TriggerClick(); }); } From 05a9160884a6426159539c9b9b7b326156cbeabd Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 6 Feb 2025 03:10:21 -0500 Subject: [PATCH 27/92] Simplify LINQ expressions to appease CI don't ask me --- .../SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs | 4 ++-- .../Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs | 2 +- .../SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs index 93472e7b81..f843c2cded 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs @@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("random beatmap", () => { - var randomSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).FirstOrDefault(); + var randomSet = beatmaps.GetAllUsableBeatmapSets().MinBy(_ => RNG.Next()); randomSet ??= TestResources.CreateTestBeatmapSetInfo(); - beatmap = randomSet.Beatmaps.OrderBy(_ => RNG.Next()).First(); + beatmap = randomSet.Beatmaps.MinBy(_ => RNG.Next())!; CreateThemedContent(OverlayColourScheme.Aquamarine); }); diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs index 540eae3be0..382357b67e 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("random beatmap", () => { - var randomSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).FirstOrDefault(); + var randomSet = beatmaps.GetAllUsableBeatmapSets().MinBy(_ => RNG.Next()); randomSet ??= TestResources.CreateTestBeatmapSetInfo(); beatmapSet = randomSet; diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs index 72f7a9e98c..41eb5c3683 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs @@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("random beatmap", () => { - var randomSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).FirstOrDefault(); + var randomSet = beatmaps.GetAllUsableBeatmapSets().MinBy(_ => RNG.Next()); randomSet ??= TestResources.CreateTestBeatmapSetInfo(); - beatmap = randomSet.Beatmaps.OrderBy(_ => RNG.Next()).First(); + beatmap = randomSet.Beatmaps.MinBy(_ => RNG.Next())!; CreateThemedContent(OverlayColourScheme.Aquamarine); }); From 3a0464299af5bde7527d48bcdb8f3a1a85d67d85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Feb 2025 19:22:57 +0900 Subject: [PATCH 28/92] Remove unnecessary V2 suffixes --- .../SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs | 4 ++-- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 4 ++-- osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 4 ++-- osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs | 8 ++++---- .../SelectV2/{TopLocalRankV2.cs => TopLocalRank.cs} | 6 ++---- ...ateBeatmapSetButtonV2.cs => UpdateBeatmapSetButton.cs} | 4 ++-- 6 files changed, 14 insertions(+), 16 deletions(-) rename osu.Game/Screens/SelectV2/{TopLocalRankV2.cs => TopLocalRank.cs} (94%) rename osu.Game/Screens/SelectV2/{UpdateBeatmapSetButtonV2.cs => UpdateBeatmapSetButton.cs} (98%) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs index 6e5d731453..ba3f2635b0 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneUpdateBeatmapSetButtonV2.cs @@ -11,12 +11,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneUpdateBeatmapSetButtonV2 : OsuTestScene { - private UpdateBeatmapSetButtonV2 button = null!; + private UpdateBeatmapSetButton button = null!; [SetUp] public void SetUp() => Schedule(() => { - Child = button = new UpdateBeatmapSetButtonV2 + Child = button = new UpdateBeatmapSetButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index a888c0331f..3db60876a1 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.SelectV2 private ConstrainedIconContainer difficultyIcon = null!; private OsuSpriteText keyCountText = null!; private StarRatingDisplay starRatingDisplay = null!; - private TopLocalRankV2 difficultyRank = null!; + private TopLocalRank difficultyRank = null!; private OsuSpriteText difficultyText = null!; private OsuSpriteText authorText = null!; @@ -118,7 +118,7 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - difficultyRank = new TopLocalRankV2 + difficultyRank = new TopLocalRank { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index 85e97a8464..6caabb79c3 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.SelectV2 private OsuSpriteText titleText = null!; private OsuSpriteText artistText = null!; private Drawable chevronIcon = null!; - private UpdateBeatmapSetButtonV2 updateButton = null!; + private UpdateBeatmapSetButton updateButton = null!; private BeatmapSetOnlineStatusPill statusPill = null!; private DifficultySpectrumDisplay difficultiesDisplay = null!; @@ -98,7 +98,7 @@ namespace osu.Game.Screens.SelectV2 Margin = new MarginPadding { Top = 5f }, Children = new Drawable[] { - updateButton = new UpdateBeatmapSetButtonV2 + updateButton = new UpdateBeatmapSetButton { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs index 32a729c95d..e8628d5b78 100644 --- a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs @@ -64,13 +64,13 @@ namespace osu.Game.Screens.SelectV2 private OsuSpriteText titleText = null!; private OsuSpriteText artistText = null!; - private UpdateBeatmapSetButtonV2 updateButton = null!; + private UpdateBeatmapSetButton updateButton = null!; private BeatmapSetOnlineStatusPill statusPill = null!; private ConstrainedIconContainer difficultyIcon = null!; private FillFlowContainer difficultyLine = null!; private StarRatingDisplay difficultyStarRating = null!; - private TopLocalRankV2 difficultyRank = null!; + private TopLocalRank difficultyRank = null!; private OsuSpriteText difficultyKeyCountText = null!; private OsuSpriteText difficultyName = null!; private OsuSpriteText difficultyAuthor = null!; @@ -121,7 +121,7 @@ namespace osu.Game.Screens.SelectV2 Margin = new MarginPadding { Top = 5f }, Children = new Drawable[] { - updateButton = new UpdateBeatmapSetButtonV2 + updateButton = new UpdateBeatmapSetButton { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -149,7 +149,7 @@ namespace osu.Game.Screens.SelectV2 Scale = new Vector2(8f / 9f), Margin = new MarginPadding { Right = 5f }, }, - difficultyRank = new TopLocalRankV2 + difficultyRank = new TopLocalRank { Scale = new Vector2(8f / 11), Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/SelectV2/TopLocalRankV2.cs b/osu.Game/Screens/SelectV2/TopLocalRank.cs similarity index 94% rename from osu.Game/Screens/SelectV2/TopLocalRankV2.cs rename to osu.Game/Screens/SelectV2/TopLocalRank.cs index 241e92a67d..2a72a05db7 100644 --- a/osu.Game/Screens/SelectV2/TopLocalRankV2.cs +++ b/osu.Game/Screens/SelectV2/TopLocalRank.cs @@ -19,7 +19,7 @@ using Realms; namespace osu.Game.Screens.SelectV2 { - public partial class TopLocalRankV2 : CompositeDrawable + public partial class TopLocalRank : CompositeDrawable { private BeatmapInfo? beatmap; @@ -48,9 +48,7 @@ namespace osu.Game.Screens.SelectV2 private readonly UpdateableRank updateable; - public ScoreRank? DisplayedRank => updateable.Rank; - - public TopLocalRankV2(BeatmapInfo? beatmap = null) + public TopLocalRank(BeatmapInfo? beatmap = null) { AutoSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs b/osu.Game/Screens/SelectV2/UpdateBeatmapSetButton.cs similarity index 98% rename from osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs rename to osu.Game/Screens/SelectV2/UpdateBeatmapSetButton.cs index 2d1ce4ba48..e2c841f88a 100644 --- a/osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs +++ b/osu.Game/Screens/SelectV2/UpdateBeatmapSetButton.cs @@ -22,7 +22,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { - public partial class UpdateBeatmapSetButtonV2 : OsuAnimatedButton + public partial class UpdateBeatmapSetButton : OsuAnimatedButton { private BeatmapSetInfo? beatmapSet; @@ -53,7 +53,7 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private IDialogOverlay? dialogOverlay { get; set; } - public UpdateBeatmapSetButtonV2() + public UpdateBeatmapSetButton() { Size = new Vector2(75f, 22f); } From 151101be7031c8b87716bbb24411f44658567482 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Feb 2025 19:24:30 +0900 Subject: [PATCH 29/92] Mark `Action` as `init` only --- osu.Game/Screens/SelectV2/CarouselPanelPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs b/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs index 4b533e362a..5aefa57bb5 100644 --- a/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs +++ b/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.SelectV2 public readonly BindableBool Active = new BindableBool(); public readonly BindableBool KeyboardActive = new BindableBool(); - public Action? Action; + public Action? Action { get; init; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { From 554884710cd4bb9749e337ed25297304cfdb3541 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Feb 2025 19:30:27 +0900 Subject: [PATCH 30/92] Rename classes for better discoverability / grouping --- ...estSceneBeatmapCarouselV2ArtistGrouping.cs | 34 ++++++------- ...ceneBeatmapCarouselV2DifficultyGrouping.cs | 50 +++++++++---------- .../TestSceneBeatmapCarouselV2NoGrouping.cs | 16 +++--- .../TestSceneBeatmapCarouselV2Scrolling.cs | 10 ++-- ...stSceneBeatmapCarouselV2DifficultyPanel.cs | 8 +-- .../TestSceneBeatmapCarouselV2GroupPanel.cs | 20 ++++---- .../TestSceneBeatmapCarouselV2SetPanel.cs | 8 +-- ...stSceneBeatmapCarouselV2StandalonePanel.cs | 8 +-- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 6 +-- .../SelectV2/BeatmapCarouselFilterGrouping.cs | 4 +- .../{BeatmapPanel.cs => PanelBeatmap.cs} | 4 +- ...{BeatmapSetPanel.cs => PanelBeatmapSet.cs} | 4 +- ...lonePanel.cs => PanelBeatmapStandalone.cs} | 4 +- .../SelectV2/{GroupPanel.cs => PanelGroup.cs} | 2 +- ...oupPanel.cs => PanelGroupStarDificulty.cs} | 2 +- 15 files changed, 90 insertions(+), 90 deletions(-) rename osu.Game/Screens/SelectV2/{BeatmapPanel.cs => PanelBeatmap.cs} (98%) rename osu.Game/Screens/SelectV2/{BeatmapSetPanel.cs => PanelBeatmapSet.cs} (98%) rename osu.Game/Screens/SelectV2/{BeatmapStandalonePanel.cs => PanelBeatmapStandalone.cs} (99%) rename osu.Game/Screens/SelectV2/{GroupPanel.cs => PanelGroup.cs} (98%) rename osu.Game/Screens/SelectV2/{StarsGroupPanel.cs => PanelGroupStarDificulty.cs} (98%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2ArtistGrouping.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2ArtistGrouping.cs index d3eeee151a..c378871eac 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2ArtistGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2ArtistGrouping.cs @@ -28,42 +28,42 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestOpenCloseGroupWithNoSelectionMouse() { - AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); - AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); - ClickVisiblePanel(0); + ClickVisiblePanel(0); - AddUntilStep("some sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); - AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("some sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); + AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); - ClickVisiblePanel(0); + ClickVisiblePanel(0); - AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); - AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); } [Test] public void TestOpenCloseGroupWithNoSelectionKeyboard() { - AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); - AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); SelectNextPanel(); Select(); - AddUntilStep("some sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); - AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("some sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); + AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True); CheckNoSelection(); Select(); - AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); - AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("no sets visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False); CheckNoSelection(); } @@ -96,10 +96,10 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection)); AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True); - ClickVisiblePanel(0); + ClickVisiblePanel(0); AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null); - ClickVisiblePanel(0); + ClickVisiblePanel(0); AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True); } @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.SongSelect // open first group Select(); CheckNoSelection(); - AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); + AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); SelectNextPanel(); Select(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs index c043fd87a9..f3c1634cb2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs @@ -29,32 +29,32 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestOpenCloseGroupWithNoSelectionMouse() { - AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); - ClickVisiblePanel(0); - AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); + ClickVisiblePanel(0); + AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); CheckNoSelection(); - ClickVisiblePanel(0); - AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + ClickVisiblePanel(0); + AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); } [Test] public void TestOpenCloseGroupWithNoSelectionKeyboard() { - AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); SelectNextPanel(); Select(); - AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); + AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True); CheckNoSelection(); Select(); - AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False); CheckNoSelection(); } @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection)); AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True); - ClickVisiblePanel(0); + ClickVisiblePanel(0); AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null); - ClickVisiblePanel(0); + ClickVisiblePanel(0); AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True); } @@ -120,18 +120,18 @@ namespace osu.Game.Tests.Visual.SongSelect SelectNextGroup(); WaitForGroupSelection(0, 0); - AddAssert("keyboard selected panel is beatmap", GetKeyboardSelectedPanel, Is.TypeOf); - AddAssert("selected panel is beatmap", GetSelectedPanel, Is.TypeOf); + AddAssert("keyboard selected panel is beatmap", GetKeyboardSelectedPanel, Is.TypeOf); + AddAssert("selected panel is beatmap", GetSelectedPanel, Is.TypeOf); - ClickVisiblePanel(0); - AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf); + ClickVisiblePanel(0); + AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf); AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False); - ClickVisiblePanel(0); - AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf); + ClickVisiblePanel(0); + AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf); AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True); - AddAssert("selected panel is still beatmap", GetSelectedPanel, Is.TypeOf); + AddAssert("selected panel is still beatmap", GetSelectedPanel, Is.TypeOf); } [Test] @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.SongSelect // open first group Select(); CheckNoSelection(); - AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); + AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); SelectNextPanel(); Select(); @@ -171,23 +171,23 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestInputHandlingWithinGaps() { - AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); // Clicks just above the first group panel should not actuate any action. - ClickVisiblePanelWithOffset(0, new Vector2(0, -(GroupPanel.HEIGHT / 2 + 1))); + ClickVisiblePanelWithOffset(0, new Vector2(0, -(PanelGroup.HEIGHT / 2 + 1))); - AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); - ClickVisiblePanelWithOffset(0, new Vector2(0, -(GroupPanel.HEIGHT / 2))); + ClickVisiblePanelWithOffset(0, new Vector2(0, -(PanelGroup.HEIGHT / 2))); - AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels().Any()); + AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels().Any()); CheckNoSelection(); // Beatmap panels expand their selection area to cover holes from spacing. - ClickVisiblePanelWithOffset(0, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + ClickVisiblePanelWithOffset(0, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1))); WaitForGroupSelection(0, 0); - ClickVisiblePanelWithOffset(1, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + ClickVisiblePanelWithOffset(1, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); WaitForGroupSelection(0, 1); } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs index 09ded342c3..b4048a5355 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs @@ -213,27 +213,27 @@ namespace osu.Game.Tests.Visual.SongSelect AddBeatmaps(2, 5); WaitForDrawablePanels(); - AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); // Clicks just above the first group panel should not actuate any action. - ClickVisiblePanelWithOffset(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2 + 1))); + ClickVisiblePanelWithOffset(0, new Vector2(0, -(PanelBeatmapSet.HEIGHT / 2 + 1))); - AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); - ClickVisiblePanelWithOffset(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2))); + ClickVisiblePanelWithOffset(0, new Vector2(0, -(PanelBeatmapSet.HEIGHT / 2))); - AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels().Any()); + AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels().Any()); WaitForSelection(0, 0); // Beatmap panels expand their selection area to cover holes from spacing. - ClickVisiblePanelWithOffset(1, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + ClickVisiblePanelWithOffset(1, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1))); WaitForSelection(0, 0); // Panels with higher depth will handle clicks in the gutters for simplicity. - ClickVisiblePanelWithOffset(2, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + ClickVisiblePanelWithOffset(2, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); WaitForSelection(0, 2); - ClickVisiblePanelWithOffset(3, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + ClickVisiblePanelWithOffset(3, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); WaitForSelection(0, 3); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Scrolling.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Scrolling.cs index ee6c11595a..890e1dd6e3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Scrolling.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Scrolling.cs @@ -30,16 +30,16 @@ namespace osu.Game.Tests.Visual.SongSelect Quad positionBefore = default; AddStep("select middle beatmap", () => Carousel.CurrentSelection = BeatmapSets.ElementAt(BeatmapSets.Count - 2).Beatmaps.First()); - AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType().Single(p => p.Selected.Value))); + AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType().Single(p => p.Selected.Value))); WaitForScrolling(); - AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad); + AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad); RemoveFirstBeatmap(); WaitForSorting(); - AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType().Single(p => p.Selected.Value).ScreenSpaceDrawQuad, + AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType().Single(p => p.Selected.Value).ScreenSpaceDrawQuad, () => Is.EqualTo(positionBefore)); } @@ -54,11 +54,11 @@ namespace osu.Game.Tests.Visual.SongSelect WaitForScrolling(); - AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad); + AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad); RemoveFirstBeatmap(); WaitForSorting(); - AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType().Single(p => p.Selected.Value).ScreenSpaceDrawQuad, + AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType().Single(p => p.Selected.Value).ScreenSpaceDrawQuad, () => Is.EqualTo(positionBefore)); } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs index f843c2cded..1947721d5d 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2DifficultyPanel.cs @@ -78,21 +78,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - new BeatmapPanel + new PanelBeatmap { Item = new CarouselItem(beatmap) }, - new BeatmapPanel + new PanelBeatmap { Item = new CarouselItem(beatmap), KeyboardSelected = { Value = true } }, - new BeatmapPanel + new PanelBeatmap { Item = new CarouselItem(beatmap), Selected = { Value = true } }, - new BeatmapPanel + new PanelBeatmap { Item = new CarouselItem(beatmap), KeyboardSelected = { Value = true }, diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs index 5c94addc74..711a3b881d 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs @@ -29,49 +29,49 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - new GroupPanel + new PanelGroup { Item = new CarouselItem(new GroupDefinition('A', "Group A")) }, - new GroupPanel + new PanelGroup { Item = new CarouselItem(new GroupDefinition('A', "Group A")), KeyboardSelected = { Value = true } }, - new GroupPanel + new PanelGroup { Item = new CarouselItem(new GroupDefinition('A', "Group A")), Expanded = { Value = true } }, - new GroupPanel + new PanelGroup { Item = new CarouselItem(new GroupDefinition('A', "Group A")), KeyboardSelected = { Value = true }, Expanded = { Value = true } }, - new StarsGroupPanel + new PanelGroupStarDificulty { Item = new CarouselItem(new GroupDefinition(1, "1")) }, - new StarsGroupPanel + new PanelGroupStarDificulty { Item = new CarouselItem(new GroupDefinition(3, "3")), Expanded = { Value = true } }, - new StarsGroupPanel + new PanelGroupStarDificulty { Item = new CarouselItem(new GroupDefinition(5, "5")), }, - new StarsGroupPanel + new PanelGroupStarDificulty { Item = new CarouselItem(new GroupDefinition(7, "7")), Expanded = { Value = true } }, - new StarsGroupPanel + new PanelGroupStarDificulty { Item = new CarouselItem(new GroupDefinition(8, "8")), }, - new StarsGroupPanel + new PanelGroupStarDificulty { Item = new CarouselItem(new GroupDefinition(9, "9")), Expanded = { Value = true } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs index 382357b67e..ef34394e12 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2SetPanel.cs @@ -68,21 +68,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - new BeatmapSetPanel + new PanelBeatmapSet { Item = new CarouselItem(beatmapSet) }, - new BeatmapSetPanel + new PanelBeatmapSet { Item = new CarouselItem(beatmapSet), KeyboardSelected = { Value = true } }, - new BeatmapSetPanel + new PanelBeatmapSet { Item = new CarouselItem(beatmapSet), Expanded = { Value = true } }, - new BeatmapSetPanel + new PanelBeatmapSet { Item = new CarouselItem(beatmapSet), KeyboardSelected = { Value = true }, diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs index 41eb5c3683..2dbe9e6cd1 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2StandalonePanel.cs @@ -78,21 +78,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - new BeatmapStandalonePanel + new PanelBeatmapStandalone { Item = new CarouselItem(beatmap) }, - new BeatmapStandalonePanel + new PanelBeatmapStandalone { Item = new CarouselItem(beatmap), KeyboardSelected = { Value = true } }, - new BeatmapStandalonePanel + new PanelBeatmapStandalone { Item = new CarouselItem(beatmap), Selected = { Value = true } }, - new BeatmapStandalonePanel + new PanelBeatmapStandalone { Item = new CarouselItem(beatmap), KeyboardSelected = { Value = true }, diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 5ae227f86c..c6bce228dc 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -267,9 +267,9 @@ namespace osu.Game.Screens.SelectV2 #region Drawable pooling - private readonly DrawablePool beatmapPanelPool = new DrawablePool(100); - private readonly DrawablePool setPanelPool = new DrawablePool(100); - private readonly DrawablePool groupPanelPool = new DrawablePool(100); + private readonly DrawablePool beatmapPanelPool = new DrawablePool(100); + private readonly DrawablePool setPanelPool = new DrawablePool(100); + private readonly DrawablePool groupPanelPool = new DrawablePool(100); private void setupPools() { diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index cb5a40918c..8f9d5cc31b 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.SelectV2 addItem(new CarouselItem(newGroup) { - DrawHeight = GroupPanel.HEIGHT, + DrawHeight = PanelGroup.HEIGHT, DepthLayer = -2, }); } @@ -85,7 +85,7 @@ namespace osu.Game.Screens.SelectV2 addItem(new CarouselItem(beatmap.BeatmapSet!) { - DrawHeight = BeatmapSetPanel.HEIGHT, + DrawHeight = PanelBeatmapSet.HEIGHT, DepthLayer = -1 }); } diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs similarity index 98% rename from osu.Game/Screens/SelectV2/BeatmapPanel.cs rename to osu.Game/Screens/SelectV2/PanelBeatmap.cs index 3db60876a1..93ef814f2e 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -23,11 +23,11 @@ using osuTK; namespace osu.Game.Screens.SelectV2 { - public partial class BeatmapPanel : PoolableDrawable, ICarouselPanel + public partial class PanelBeatmap : PoolableDrawable, ICarouselPanel { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel + // todo: this should be replaced with information from CarouselItem about how deep is PanelBeatmap in the carousel // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). private const float difficulty_x_offset = 100f; // constant X offset for beatmap difficulty panels specifically. diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs similarity index 98% rename from osu.Game/Screens/SelectV2/BeatmapSetPanel.cs rename to osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index 6caabb79c3..2904cda9de 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -19,11 +19,11 @@ using osuTK; namespace osu.Game.Screens.SelectV2 { - public partial class BeatmapSetPanel : PoolableDrawable, ICarouselPanel + public partial class PanelBeatmapSet : PoolableDrawable, ICarouselPanel { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel + // todo: this should be replaced with information from CarouselItem about how deep is PanelBeatmap in the carousel // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. diff --git a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs similarity index 99% rename from osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs rename to osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index e8628d5b78..c858e039ec 100644 --- a/osu.Game/Screens/SelectV2/BeatmapStandalonePanel.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -25,11 +25,11 @@ using osuTK; namespace osu.Game.Screens.SelectV2 { - public partial class BeatmapStandalonePanel : PoolableDrawable, ICarouselPanel + public partial class PanelBeatmapStandalone : PoolableDrawable, ICarouselPanel { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - // todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel + // todo: this should be replaced with information from CarouselItem about how deep is PanelBeatmap in the carousel // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). private const float standalone_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/PanelGroup.cs similarity index 98% rename from osu.Game/Screens/SelectV2/GroupPanel.cs rename to osu.Game/Screens/SelectV2/PanelGroup.cs index 506a230cb4..cdd0695147 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/PanelGroup.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { - public partial class GroupPanel : PoolableDrawable, ICarouselPanel + public partial class PanelGroup : PoolableDrawable, ICarouselPanel { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; diff --git a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs b/osu.Game/Screens/SelectV2/PanelGroupStarDificulty.cs similarity index 98% rename from osu.Game/Screens/SelectV2/StarsGroupPanel.cs rename to osu.Game/Screens/SelectV2/PanelGroupStarDificulty.cs index 7e2647ccbf..2215e643bd 100644 --- a/osu.Game/Screens/SelectV2/StarsGroupPanel.cs +++ b/osu.Game/Screens/SelectV2/PanelGroupStarDificulty.cs @@ -22,7 +22,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { - public partial class StarsGroupPanel : PoolableDrawable, ICarouselPanel + public partial class PanelGroupStarDificulty : PoolableDrawable, ICarouselPanel { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; From d5566831d22fb170e1a21e522c84863eb788cd7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Feb 2025 15:06:35 +0900 Subject: [PATCH 31/92] Stop beat divisor "slider" from accepting focus --- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 43a2abe4c4..b8f2695259 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -398,6 +398,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly BindableBeatDivisor beatDivisor; + public override bool AcceptsFocus => false; + public TickSliderBar(BindableBeatDivisor beatDivisor) { CurrentNumber.BindTo(this.beatDivisor = beatDivisor); From 2738221c0b9ab579623a02d9f9b6ef7d0cd45dd6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Feb 2025 15:07:21 +0900 Subject: [PATCH 32/92] Update framework --- 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 6bbd432ee7..f4d49763ab 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index ca2604858c..0d95dfbd06 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From db4a4a1723b48f64bb88c5289c143f9b13705e0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Feb 2025 15:09:51 +0900 Subject: [PATCH 33/92] Minor bump some packages --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- ...osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Desktop/osu.Desktop.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 2 +- .../TestSceneAddPlaylistToCollectionButton.cs | 7 +++++-- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- .../Profile/Header/Components/FollowersButton.cs | 2 +- osu.Game/osu.Game.csproj | 16 ++++++++-------- 15 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 1d368e9bd1..86f73a37d4 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index d69bc78b8f..51c0233942 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 7ac269f65f..ed4e8631ea 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index d69bc78b8f..51c0233942 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 21c570a7b2..05d5bb19fb 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 56ee208670..fc1b13f3ad 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 5e4bad279b..edb01b044e 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 267dc98985..6510568555 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 523df4c259..e498989a79 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 88b482ab4c..8c4fcc461c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -11,6 +11,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -52,7 +53,6 @@ using osu.Game.Tests.Resources; using osu.Game.Utils; using osuTK; using osuTK.Input; -using SharpCompress; namespace osu.Game.Tests.Visual.Navigation { diff --git a/osu.Game.Tests/Visual/Playlists/TestSceneAddPlaylistToCollectionButton.cs b/osu.Game.Tests/Visual/Playlists/TestSceneAddPlaylistToCollectionButton.cs index 46c93d9ae2..abfc5c4d0e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestSceneAddPlaylistToCollectionButton.cs +++ b/osu.Game.Tests/Visual/Playlists/TestSceneAddPlaylistToCollectionButton.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Playlists; using osuTK; using osuTK.Input; -using SharpCompress; namespace osu.Game.Tests.Visual.Playlists { @@ -53,7 +52,11 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear realm", () => Realm.Realm.Write(() => Realm.Realm.RemoveAll())); - AddStep("clear notifications", () => notificationOverlay.AllNotifications.Empty()); + AddStep("clear notifications", () => + { + foreach (var notification in notificationOverlay.AllNotifications) + notification.Close(runFlingAnimation: false); + }); importBeatmap(); diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index e78a3ea4f3..a1f43505f0 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 1daf5a446e..8437a1bc4e 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -4,7 +4,7 @@ osu.Game.Tournament.Tests.TournamentTestRunner - + diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index c4425643fd..b93f996ec2 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -16,7 +17,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; -using SharpCompress; namespace osu.Game.Overlays.Profile.Header.Components { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index edf471ce8f..3793efd829 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,12 +22,12 @@ - - - - - - + + + + + + @@ -37,9 +37,9 @@ - + - + From eaf36796213de0c446e89115b5f71a757a06e959 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Feb 2025 17:17:07 +0900 Subject: [PATCH 34/92] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3793efd829..6b5392eec6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 8423d9de9b6447a42110ca69136c236175431439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Feb 2025 09:39:43 +0100 Subject: [PATCH 35/92] Fix distance snap grid colours being off-by-one in certain cases Closes https://github.com/ppy/osu/issues/31909. Previously: https://github.com/ppy/osu/pull/30062. Happening because of rounding errors - in this case the beat index pre-flooring was something like a 0.003 off of a full beat, which would get floored down rather than rounded up which created the discrepancy. But also we don't want to round *too* far, which is why this frankenstein solution has to exist I think. This is probably all exacerbated by stable not handling decimal control point start times. Would add tests if not for the fact that this is like extremely annoying to test. --- .../Edit/Compose/Components/DistanceSnapGrid.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index dd1671cfdd..88e28df8e3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Layout; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Types; @@ -148,7 +149,18 @@ namespace osu.Game.Screens.Edit.Compose.Components { var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); double beatLength = timingPoint.BeatLength / beatDivisor.Value; - int beatIndex = (int)Math.Floor((StartTime - timingPoint.Time) / beatLength); + double fractionalBeatIndex = (StartTime - timingPoint.Time) / beatLength; + int beatIndex = (int)Math.Round(fractionalBeatIndex); + // `fractionalBeatIndex` could differ from `beatIndex` for two reasons: + // - rounding errors (which can be exacerbated by timing point start times being truncated by/for stable), + // - `StartTime` is not snapped to the beat. + // in case 1, we want rounding to occur to prevent an off-by-one, + // as `StartTime` *is* quantised to the beat. but it just doesn't look like it because floats do float things. + // in case 2, we want *flooring* to occur, to prevent a possible off-by-one + // because of the rounding snapping forward by a chunk of time significantly too high to be considered a rounding error. + // the tolerance margin chosen here is arbitrary and can be adjusted if more cases of this are found. + if (Precision.DefinitelyBigger(beatIndex, fractionalBeatIndex, 0.005)) + beatIndex = (int)Math.Floor(fractionalBeatIndex); var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours); From 2b4b21beb6c50b12e0daf4031b1dcb4fab75b3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Feb 2025 09:45:09 +0100 Subject: [PATCH 36/92] Fix distance snap grid line opacity being incorrect on non-1.0x velocities Noticed in passing. --- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 164a209958..8c7afd2aeb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components const float thickness = 4; float diameter = (offset + (i + 1) * DistanceBetweenTicks + thickness / 2) * 2; - AddInternal(new Ring(StartTime, GetColourForIndexFromPlacement(i)) + AddInternal(new Ring(StartTime, GetColourForIndexFromPlacement(i), SliderVelocitySource) { Position = StartPosition, Origin = Anchor.Centre, @@ -128,12 +128,14 @@ namespace osu.Game.Screens.Edit.Compose.Components private EditorClock? editorClock { get; set; } private readonly double startTime; + private readonly IHasSliderVelocity? sliderVelocitySource; private readonly Color4 baseColour; - public Ring(double startTime, Color4 baseColour) + public Ring(double startTime, Color4 baseColour, IHasSliderVelocity? sliderVelocitySource) { this.startTime = startTime; + this.sliderVelocitySource = sliderVelocitySource; Colour = this.baseColour = baseColour; @@ -150,7 +152,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float distanceSpacingMultiplier = (float)snapProvider.DistanceSpacingMultiplier.Value; double timeFromReferencePoint = editorClock.CurrentTime - startTime; - float distanceForCurrentTime = snapProvider.DurationToDistance(timeFromReferencePoint, startTime) + float distanceForCurrentTime = snapProvider.DurationToDistance(timeFromReferencePoint, startTime, sliderVelocitySource) * distanceSpacingMultiplier; float timeBasedAlpha = 1 - Math.Clamp(Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1); From 5304ea2446b922cbfccef4bbefb058e30c224590 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Feb 2025 22:42:03 +0900 Subject: [PATCH 37/92] Fix minor typo --- osu.Game/Localisation/BeatmapSubmissionStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/BeatmapSubmissionStrings.cs b/osu.Game/Localisation/BeatmapSubmissionStrings.cs index 3abe8cc515..0cf0498daa 100644 --- a/osu.Game/Localisation/BeatmapSubmissionStrings.cs +++ b/osu.Game/Localisation/BeatmapSubmissionStrings.cs @@ -140,9 +140,9 @@ namespace osu.Game.Localisation public static LocalisableString LoadInBrowserAfterSubmission => new TranslatableString(getKey(@"load_in_browser_after_submission"), @"Load in browser after submission"); /// - /// "Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost." + /// "Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost." /// - public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."); + public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."); /// /// "Empty beatmaps cannot be submitted." From f37a56c3079ac78935069d7135c68a42d9dcc59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Feb 2025 15:01:05 +0100 Subject: [PATCH 38/92] Fix nudge operations incurring FP error from coordinate space conversions Closes https://github.com/ppy/osu/issues/31915. Reproduction of aforementioned issue requires 1280x720 resolution, which should also be a good way to confirm that this does anything. To me this is also equal-parts-bugfix, equal-parts-code-quality PR, because tell me: what on earth was this code ever doing at `ComposeBlueprintContainer` level? Nudging by one playfield-space-unit doesn't even *make sense* in something like taiko or mania. --- .../Edit/CatchSelectionHandler.cs | 62 +++++++++++++++++ .../Edit/OsuSelectionHandler.cs | 62 +++++++++++++++++ .../Components/ComposeBlueprintContainer.cs | 66 ------------------- 3 files changed, 124 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index a2784126eb..a7cd84aed5 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; @@ -12,6 +13,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using osuTK.Input; using Direction = osu.Framework.Graphics.Direction; namespace osu.Game.Rulesets.Catch.Edit @@ -38,6 +40,13 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } + moveSelection(deltaX); + + return true; + } + + private void moveSelection(float deltaX) + { EditorBeatmap.PerformOnSelection(h => { if (!(h is CatchHitObject catchObject)) return; @@ -48,7 +57,60 @@ namespace osu.Game.Rulesets.Catch.Edit foreach (var nested in catchObject.NestedHitObjects.OfType()) nested.OriginalX += deltaX; }); + } + private bool nudgeMovementActive; + + protected override bool OnKeyDown(KeyDownEvent e) + { + // Until the keys below are global actions, this will prevent conflicts with "seek between sample points" + // which has a default of ctrl+shift+arrows. + if (e.ShiftPressed) + return false; + + if (e.ControlPressed) + { + switch (e.Key) + { + case Key.Left: + return nudgeSelection(-1); + + case Key.Right: + return nudgeSelection(1); + } + } + + return false; + } + + protected override void OnKeyUp(KeyUpEvent e) + { + base.OnKeyUp(e); + + if (nudgeMovementActive && !e.ControlPressed) + { + EditorBeatmap.EndChange(); + nudgeMovementActive = false; + } + } + + /// + /// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints). + /// + private bool nudgeSelection(float deltaX) + { + if (!nudgeMovementActive) + { + nudgeMovementActive = true; + EditorBeatmap.BeginChange(); + } + + var firstBlueprint = SelectedBlueprints.FirstOrDefault(); + + if (firstBlueprint == null) + return false; + + moveSelection(deltaX); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index bac0a5e273..3a1ff34fb9 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } + private bool nudgeMovementActive; + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed) @@ -48,9 +50,43 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + // Until the keys below are global actions, this will prevent conflicts with "seek between sample points" + // which has a default of ctrl+shift+arrows. + if (e.ShiftPressed) + return false; + + if (e.ControlPressed) + { + switch (e.Key) + { + case Key.Left: + return nudgeSelection(new Vector2(-1, 0)); + + case Key.Right: + return nudgeSelection(new Vector2(1, 0)); + + case Key.Up: + return nudgeSelection(new Vector2(0, -1)); + + case Key.Down: + return nudgeSelection(new Vector2(0, 1)); + } + } + return false; } + protected override void OnKeyUp(KeyUpEvent e) + { + base.OnKeyUp(e); + + if (nudgeMovementActive && !e.ControlPressed) + { + EditorBeatmap.EndChange(); + nudgeMovementActive = false; + } + } + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var hitObjects = selectedMovableObjects; @@ -70,6 +106,13 @@ namespace osu.Game.Rulesets.Osu.Edit if (hitObjects.Any(h => Precision.AlmostEquals(localDelta, -h.StackOffset))) return true; + moveObjects(hitObjects, localDelta); + + return true; + } + + private void moveObjects(OsuHitObject[] hitObjects, Vector2 localDelta) + { // this will potentially move the selection out of bounds... foreach (var h in hitObjects) h.Position += localDelta; @@ -81,7 +124,26 @@ namespace osu.Game.Rulesets.Osu.Edit // this intentionally bypasses the editor `UpdateState()` / beatmap processor flow for performance reasons, // as the entire flow is too expensive to run on every movement. Scheduler.AddOnce(OsuBeatmapProcessor.ApplyStacking, EditorBeatmap); + } + /// + /// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints). + /// + /// + private bool nudgeSelection(Vector2 delta) + { + if (!nudgeMovementActive) + { + nudgeMovementActive = true; + EditorBeatmap.BeginChange(); + } + + var firstBlueprint = SelectedBlueprints.FirstOrDefault(); + + if (firstBlueprint == null) + return false; + + moveObjects(selectedMovableObjects, delta); return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index e82f6395d0..4c57eee971 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -27,7 +27,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -112,71 +111,6 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.DrawableObject = drawableObject; } - private bool nudgeMovementActive; - - protected override bool OnKeyDown(KeyDownEvent e) - { - // Until the keys below are global actions, this will prevent conflicts with "seek between sample points" - // which has a default of ctrl+shift+arrows. - if (e.ShiftPressed) - return false; - - if (e.ControlPressed) - { - switch (e.Key) - { - case Key.Left: - return nudgeSelection(new Vector2(-1, 0)); - - case Key.Right: - return nudgeSelection(new Vector2(1, 0)); - - case Key.Up: - return nudgeSelection(new Vector2(0, -1)); - - case Key.Down: - return nudgeSelection(new Vector2(0, 1)); - } - } - - return false; - } - - protected override void OnKeyUp(KeyUpEvent e) - { - base.OnKeyUp(e); - - if (nudgeMovementActive && !e.ControlPressed) - { - Beatmap.EndChange(); - nudgeMovementActive = false; - } - } - - /// - /// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints). - /// - /// - private bool nudgeSelection(Vector2 delta) - { - if (!nudgeMovementActive) - { - nudgeMovementActive = true; - Beatmap.BeginChange(); - } - - var firstBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(); - - if (firstBlueprint == null) - return false; - - // convert to game space coordinates - delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); - - SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, delta)); - return true; - } - private void updatePlacementNewCombo() { if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c) From f5b485a44d1fb35be22c1b224837798b989248fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 12:58:54 +0900 Subject: [PATCH 39/92] Stop "hold for HUD" key binding from blocking other key presses I don't think there's a good reason for this to be blocking. Closes https://github.com/ppy/osu/issues/31274. --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f670e2f628..8bfa8dd6ff 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -414,7 +414,7 @@ namespace osu.Game.Screens.Play case GlobalAction.HoldForHUD: holdingForHUD.Value = true; - return true; + return false; case GlobalAction.ToggleInGameInterface: switch (configVisibilityMode.Value) From 20dbe096e03e043143388eab62e1650a3be1ea2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 13:04:38 +0900 Subject: [PATCH 40/92] Refactor slightly --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index 73d0403e3f..d331b691d5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -19,15 +19,18 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(OsuModTargetPractice), }).ToArray(); - [SettingSource("Fail when missing on a slider tail")] - public BindableBool SliderTailMiss { get; } = new BindableBool(); + [SettingSource("Also fail when missing a slider tail")] + public BindableBool FailOnSliderTail { get; } = new BindableBool(); protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) { - if (SliderTailMiss.Value && result.HitObject is SliderTailCircle && result.Type == HitResult.IgnoreMiss) + if (base.FailCondition(healthProcessor, result)) return true; - return base.FailCondition(healthProcessor, result); + if (FailOnSliderTail.Value && result.HitObject is SliderTailCircle && !result.IsHit) + return true; + + return false; } } } From 2d8e35be32a923049c717b3ae5906804097d67b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 13:08:33 +0900 Subject: [PATCH 41/92] Add test coverage --- .../Mods/TestSceneOsuModSuddenDeath.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs index 688cf70f71..23dd2123c3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs @@ -24,11 +24,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { } - [Test] - public void TestMissTail() => CreateModTest(new ModTestData + [TestCase(true)] + [TestCase(false)] + public void TestMissTail(bool tailMiss) => CreateModTest(new ModTestData { - Mod = new OsuModSuddenDeath(), - PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), + Mod = new OsuModSuddenDeath + { + FailOnSliderTail = { Value = tailMiss } + }, + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(tailMiss), Autoplay = false, CreateBeatmap = () => new Beatmap { From 77e40140e5b5fc1c83892492e9809dc4b1b708e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 13:41:30 +0900 Subject: [PATCH 42/92] Fix selected sliders sometimes not being clickable in editor Closes https://github.com/ppy/osu/issues/31918. Regressed with https://github.com/ppy/osu/commit/1648f2efa306f587714178f113e69d8ad8c4ac02 for obvious reasons. --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index f7c25b43dd..39c0681dba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -626,7 +626,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { - if (BodyPiece.ReceivePositionalInputAt(screenSpacePos) && DrawableObject.Body.Alpha > 0) + if (BodyPiece.ReceivePositionalInputAt(screenSpacePos) && (IsSelected || DrawableObject.Body.Alpha > 0)) return true; if (ControlPointVisualiser == null) From 8e25c9445234616f10053b5f2bba193e10444da9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 14:12:14 +0900 Subject: [PATCH 43/92] Fix kiai fountains sometimes not displaying when they should MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous logic was very wrong, as the check would only occur on each beat. But that's not how kiai sections work – they can be placed at any timestamp, even if that doesn't align with a beat. In addition, the rate limiting has been removed because it didn't exist on stable and causes some fountains to be missed. Overlap scenarios are already handled internally by the `StarFountain` class. Closes https://github.com/ppy/osu/issues/31855. --- .../Containers/BeatSyncedContainer.cs | 37 +++++++++++-------- osu.Game/Screens/Menu/KiaiMenuFountains.cs | 19 +++------- .../Screens/Play/KiaiGameplayFountains.cs | 21 ++++------- 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 7210371ebf..4331b91e61 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -73,6 +73,16 @@ namespace osu.Game.Graphics.Containers /// protected bool IsBeatSyncedWithTrack { get; private set; } + /// + /// The most valid timing point, updated every frame. + /// + protected TimingControlPoint TimingPoint { get; private set; } = TimingControlPoint.DEFAULT; + + /// + /// The most valid effect point, updated every frame. + /// + protected EffectControlPoint EffectPoint { get; private set; } = EffectControlPoint.DEFAULT; + [Resolved] protected IBeatSyncProvider BeatSyncSource { get; private set; } = null!; @@ -82,9 +92,6 @@ namespace osu.Game.Graphics.Containers protected override void Update() { - TimingControlPoint timingPoint; - EffectControlPoint effectPoint; - IsBeatSyncedWithTrack = BeatSyncSource.Clock.IsRunning; double currentTrackTime; @@ -102,8 +109,8 @@ namespace osu.Game.Graphics.Containers currentTrackTime = BeatSyncSource.Clock.CurrentTime + early; - timingPoint = BeatSyncSource.ControlPoints?.TimingPointAt(currentTrackTime) ?? TimingControlPoint.DEFAULT; - effectPoint = BeatSyncSource.ControlPoints?.EffectPointAt(currentTrackTime) ?? EffectControlPoint.DEFAULT; + TimingPoint = BeatSyncSource.ControlPoints?.TimingPointAt(currentTrackTime) ?? TimingControlPoint.DEFAULT; + EffectPoint = BeatSyncSource.ControlPoints?.EffectPointAt(currentTrackTime) ?? EffectControlPoint.DEFAULT; } else { @@ -111,28 +118,28 @@ namespace osu.Game.Graphics.Containers // we still want to show an idle animation, so use this container's time instead. currentTrackTime = Clock.CurrentTime + EarlyActivationMilliseconds; - timingPoint = TimingControlPoint.DEFAULT; - effectPoint = EffectControlPoint.DEFAULT; + TimingPoint = TimingControlPoint.DEFAULT; + EffectPoint = EffectControlPoint.DEFAULT; } - double beatLength = timingPoint.BeatLength / Divisor; + double beatLength = TimingPoint.BeatLength / Divisor; while (beatLength < MinimumBeatLength) beatLength *= 2; - int beatIndex = (int)((currentTrackTime - timingPoint.Time) / beatLength) - (timingPoint.OmitFirstBarLine ? 1 : 0); + int beatIndex = (int)((currentTrackTime - TimingPoint.Time) / beatLength) - (TimingPoint.OmitFirstBarLine ? 1 : 0); // The beats before the start of the first control point are off by 1, this should do the trick - if (currentTrackTime < timingPoint.Time) + if (currentTrackTime < TimingPoint.Time) beatIndex--; - TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % beatLength; + TimeUntilNextBeat = (TimingPoint.Time - currentTrackTime) % beatLength; if (TimeUntilNextBeat <= 0) TimeUntilNextBeat += beatLength; TimeSinceLastBeat = beatLength - TimeUntilNextBeat; - if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat) + if (ReferenceEquals(TimingPoint, lastTimingPoint) && beatIndex == lastBeat) return; // as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat. @@ -140,13 +147,13 @@ namespace osu.Game.Graphics.Containers if (AllowMistimedEventFiring || Math.Abs(TimeSinceLastBeat) < MISTIMED_ALLOWANCE) { using (BeginDelayedSequence(-TimeSinceLastBeat)) - OnNewBeat(beatIndex, timingPoint, effectPoint, BeatSyncSource.CurrentAmplitudes); + OnNewBeat(beatIndex, TimingPoint, EffectPoint, BeatSyncSource.CurrentAmplitudes); } lastBeat = beatIndex; - lastTimingPoint = timingPoint; + lastTimingPoint = TimingPoint; - IsKiaiTime = effectPoint.KiaiMode; + IsKiaiTime = EffectPoint.KiaiMode; } } } diff --git a/osu.Game/Screens/Menu/KiaiMenuFountains.cs b/osu.Game/Screens/Menu/KiaiMenuFountains.cs index 07c06dcdb9..7978e9fa91 100644 --- a/osu.Game/Screens/Menu/KiaiMenuFountains.cs +++ b/osu.Game/Screens/Menu/KiaiMenuFountains.cs @@ -3,10 +3,8 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Utils; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Menu @@ -40,27 +38,22 @@ namespace osu.Game.Screens.Menu private bool isTriggered; - private double? lastTrigger; - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + protected override void Update() { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + base.Update(); - if (effectPoint.KiaiMode && !isTriggered) + if (EffectPoint.KiaiMode && !isTriggered) { - bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - effectPoint.Time) < 500; + bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - EffectPoint.Time) < 500; if (isNearEffectPoint) Shoot(); } - isTriggered = effectPoint.KiaiMode; + isTriggered = EffectPoint.KiaiMode; } public void Shoot() { - if (lastTrigger != null && Clock.CurrentTime - lastTrigger < 500) - return; - int direction = RNG.Next(-1, 2); switch (direction) @@ -80,8 +73,6 @@ namespace osu.Game.Screens.Menu rightFountain.Shoot(1); break; } - - lastTrigger = Clock.CurrentTime; } } } diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index fd9596c838..19a9c2b6e5 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -1,15 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable using System; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Screens.Menu; @@ -48,33 +46,28 @@ namespace osu.Game.Screens.Play private bool isTriggered; - private double? lastTrigger; - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + protected override void Update() { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + base.Update(); if (!kiaiStarFountains.Value) return; - if (effectPoint.KiaiMode && !isTriggered) + if (EffectPoint.KiaiMode && !isTriggered) { - bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - effectPoint.Time) < 500; + Logger.Log("shooting"); + bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - EffectPoint.Time) < 500; if (isNearEffectPoint) Shoot(); } - isTriggered = effectPoint.KiaiMode; + isTriggered = EffectPoint.KiaiMode; } public void Shoot() { - if (lastTrigger != null && Clock.CurrentTime - lastTrigger < 500) - return; - leftFountain.Shoot(1); rightFountain.Shoot(-1); - lastTrigger = Clock.CurrentTime; } public partial class GameplayStarFountain : StarFountain From 88ec204d264f17020d75e54eb2b6430361f35995 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 16:22:57 +0900 Subject: [PATCH 44/92] User inheritance to avoid `Piece` structural nightmare --- .../SongSelect/BeatmapCarouselV2TestScene.cs | 2 +- .../TestSceneBeatmapCarouselV2GroupPanel.cs | 12 +- .../{CarouselPanelPiece.cs => PanelBase.cs} | 58 +++-- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 158 ++++++------- osu.Game/Screens/SelectV2/PanelBeatmapSet.cs | 68 +++--- .../SelectV2/PanelBeatmapStandalone.cs | 221 ++++++++---------- osu.Game/Screens/SelectV2/PanelGroup.cs | 111 ++++----- .../SelectV2/PanelGroupStarDifficulty.cs | 149 ++++++++++++ .../SelectV2/PanelGroupStarDificulty.cs | 187 --------------- 9 files changed, 425 insertions(+), 541 deletions(-) rename osu.Game/Screens/SelectV2/{CarouselPanelPiece.cs => PanelBase.cs} (86%) create mode 100644 osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs delete mode 100644 osu.Game/Screens/SelectV2/PanelGroupStarDificulty.cs diff --git a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs index 2c422e0a85..2c902a466f 100644 --- a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs +++ b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.SongSelect .Where(p => ((ICarouselPanel)p).Item?.IsVisible == true) .OrderBy(p => p.Y) .ElementAt(index) - .ChildrenOfType().Single() + .ChildrenOfType().Single() .TriggerClick(); }); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs index 711a3b881d..9b07f01e52 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselV2GroupPanel.cs @@ -49,29 +49,29 @@ namespace osu.Game.Tests.Visual.SongSelectV2 KeyboardSelected = { Value = true }, Expanded = { Value = true } }, - new PanelGroupStarDificulty + new PanelGroupStarDifficulty { Item = new CarouselItem(new GroupDefinition(1, "1")) }, - new PanelGroupStarDificulty + new PanelGroupStarDifficulty { Item = new CarouselItem(new GroupDefinition(3, "3")), Expanded = { Value = true } }, - new PanelGroupStarDificulty + new PanelGroupStarDifficulty { Item = new CarouselItem(new GroupDefinition(5, "5")), }, - new PanelGroupStarDificulty + new PanelGroupStarDifficulty { Item = new CarouselItem(new GroupDefinition(7, "7")), Expanded = { Value = true } }, - new PanelGroupStarDificulty + new PanelGroupStarDifficulty { Item = new CarouselItem(new GroupDefinition(8, "8")), }, - new PanelGroupStarDificulty + new PanelGroupStarDifficulty { Item = new CarouselItem(new GroupDefinition(9, "9")), Expanded = { Value = true } diff --git a/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs b/osu.Game/Screens/SelectV2/PanelBase.cs similarity index 86% rename from osu.Game/Screens/SelectV2/CarouselPanelPiece.cs rename to osu.Game/Screens/SelectV2/PanelBase.cs index 5aefa57bb5..d5a087dbb2 100644 --- a/osu.Game/Screens/SelectV2/CarouselPanelPiece.cs +++ b/osu.Game/Screens/SelectV2/PanelBase.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -9,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; 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.Game.Graphics; @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { - public partial class CarouselPanelPiece : Container + public abstract partial class PanelBase : PoolableDrawable, ICarouselPanel { private const float corner_radius = 10; @@ -43,7 +43,7 @@ namespace osu.Game.Screens.SelectV2 public Container TopLevelContent { get; } - protected override Container Content { get; } + protected Container Content { get; } public Drawable Background { @@ -67,11 +67,6 @@ namespace osu.Game.Screens.SelectV2 } } - public readonly BindableBool Active = new BindableBool(); - public readonly BindableBool KeyboardActive = new BindableBool(); - - public Action? Action { get; init; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { var inputRectangle = TopLevelContent.DrawRectangle; @@ -82,7 +77,7 @@ namespace osu.Game.Screens.SelectV2 return inputRectangle.Contains(TopLevelContent.ToLocalSpace(screenSpacePos)); } - public CarouselPanelPiece(float panelXOffset) + protected PanelBase(float panelXOffset = 0) { this.panelXOffset = panelXOffset; @@ -183,8 +178,17 @@ namespace osu.Game.Screens.SelectV2 { base.LoadComplete(); - Active.BindValueChanged(_ => updateDisplay()); - KeyboardActive.BindValueChanged(_ => updateDisplay(), true); + Expanded.BindValueChanged(_ => updateDisplay()); + KeyboardSelected.BindValueChanged(_ => updateDisplay(), true); + } + + [Resolved] + private BeatmapCarousel? carousel { get; set; } + + protected override bool OnClick(ClickEvent e) + { + carousel?.Activate(Item!); + return true; } public void Flash() @@ -194,7 +198,7 @@ namespace osu.Game.Screens.SelectV2 private void updateDisplay() { - backgroundLayer.TransformTo(nameof(Padding), backgroundLayer.Padding with { Vertical = Active.Value ? 2f : 0f }, duration, Easing.OutQuint); + backgroundLayer.TransformTo(nameof(Padding), backgroundLayer.Padding with { Vertical = Expanded.Value ? 2f : 0f }, duration, Easing.OutQuint); var backgroundColour = accentColour ?? Color4.White; var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF"); @@ -202,7 +206,7 @@ namespace osu.Game.Screens.SelectV2 backgroundAccentGradient.FadeColour(ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)), duration, Easing.OutQuint); backgroundBorder.FadeColour(backgroundColour, duration, Easing.OutQuint); - TopLevelContent.FadeEdgeEffectTo(Active.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); + TopLevelContent.FadeEdgeEffectTo(Expanded.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint); updateXOffset(); updateHover(); @@ -212,10 +216,10 @@ namespace osu.Game.Screens.SelectV2 { float x = panelXOffset + active_x_offset + keyboard_active_x_offset + left_edge_x_offset; - if (Active.Value) + if (Expanded.Value) x -= active_x_offset; - if (KeyboardActive.Value) + if (KeyboardSelected.Value) x -= keyboard_active_x_offset; this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint); @@ -223,7 +227,7 @@ namespace osu.Game.Screens.SelectV2 private void updateHover() { - bool hovered = IsHovered || KeyboardActive.Value; + bool hovered = IsHovered || KeyboardSelected.Value; if (hovered) hoverLayer.FadeIn(100, Easing.OutQuint); @@ -243,17 +247,27 @@ namespace osu.Game.Screens.SelectV2 base.OnHoverLost(e); } - protected override bool OnClick(ClickEvent e) - { - Action?.Invoke(); - return true; - } - protected override void Update() { base.Update(); Content.Padding = Content.Padding with { Left = iconContainer.DrawWidth }; backgroundLayerHorizontalPadding.Padding = new MarginPadding { Left = iconContainer.DrawWidth }; } + + #region ICarouselPanel + + public CarouselItem? Item { get; set; } + public BindableBool Selected { get; } = new BindableBool(); + public BindableBool Expanded { get; } = new BindableBool(); + public BindableBool KeyboardSelected { get; } = new BindableBool(); + + public double DrawYPosition { get; set; } + + public virtual void Activated() + { + activationFlash.FadeOutFromOne(500, Easing.OutQuint); + } + + #endregion } } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 93ef814f2e..48d15f6857 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -23,7 +22,7 @@ using osuTK; namespace osu.Game.Screens.SelectV2 { - public partial class PanelBeatmap : PoolableDrawable, ICarouselPanel + public partial class PanelBeatmap : PanelBase { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; @@ -33,7 +32,6 @@ namespace osu.Game.Screens.SelectV2 private const float duration = 500; - private CarouselPanelPiece panel = null!; private StarCounter starCounter = null!; private ConstrainedIconContainer difficultyIcon = null!; private OsuSpriteText keyCountText = null!; @@ -54,9 +52,6 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; - [Resolved] - private BeatmapCarousel? carousel { get; set; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { var inputRectangle = DrawRectangle; @@ -86,84 +81,81 @@ namespace osu.Game.Screens.SelectV2 Width = 1f; Height = HEIGHT; - InternalChild = panel = new CarouselPanelPiece(difficulty_x_offset) + Icon = difficultyIcon = new ConstrainedIconContainer { - Action = () => carousel?.Activate(Item!), - Icon = difficultyIcon = new ConstrainedIconContainer + Size = new Vector2(20), + Margin = new MarginPadding { Horizontal = 5f }, + Colour = colourProvider.Background5, + }; + + Content.Children = new[] + { + new FillFlowContainer { - Size = new Vector2(20), - Margin = new MarginPadding { Horizontal = 5f }, - Colour = colourProvider.Background5, - }, - Children = new[] - { - new FillFlowContainer + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding { Left = 10f }, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding { Left = 10f }, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + new FillFlowContainer { - new FillFlowContainer + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) { - starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - difficultyRank = new TopLocalRank - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.75f) - }, - starCounter = new StarCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.4f) - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + difficultyRank = new TopLocalRank + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.75f) + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.4f) } - }, - new FillFlowContainer + } + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new[] { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Children = new[] + keyCountText = new OsuSpriteText { - keyCountText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Alpha = 0, - }, - difficultyText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Right = 8f }, - }, - authorText = new OsuSpriteText - { - Colour = colourProvider.Content2, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft - } + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + }, + difficultyText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 8f }, + }, + authorText = new OsuSpriteText + { + Colour = colourProvider.Content2, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft } } } - }, - } + } + }, }; } @@ -183,8 +175,8 @@ namespace osu.Game.Screens.SelectV2 updateKeyCount(); }, true); - Selected.BindValueChanged(s => panel.Active.Value = s.NewValue, true); - KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); + Selected.BindValueChanged(s => Expanded.Value = s.NewValue, true); + KeyboardSelected.BindValueChanged(k => KeyboardSelected.Value = k.NewValue, true); } protected override void PrepareForUse() @@ -261,23 +253,7 @@ namespace osu.Game.Screens.SelectV2 var starRatingColour = colours.ForStarDifficulty(starDifficulty.Stars); starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint); - panel.AccentColour = starRatingColour; + AccentColour = starRatingColour; } - - #region ICarouselPanel - - public CarouselItem? Item { get; set; } - public BindableBool Selected { get; } = new BindableBool(); - public BindableBool Expanded { get; } = new BindableBool(); - public BindableBool KeyboardSelected { get; } = new BindableBool(); - - public double DrawYPosition { get; set; } - - public void Activated() - { - panel.Flash(); - } - - #endregion } } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index 2904cda9de..742fe6b6e6 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -4,10 +4,8 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -19,7 +17,7 @@ using osuTK; namespace osu.Game.Screens.SelectV2 { - public partial class PanelBeatmapSet : PoolableDrawable, ICarouselPanel + public partial class PanelBeatmapSet : PanelBase { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; @@ -29,7 +27,6 @@ namespace osu.Game.Screens.SelectV2 private const float duration = 500; - private CarouselPanelPiece panel = null!; private BeatmapSetPanelBackground background = null!; private OsuSpriteText titleText = null!; @@ -39,15 +36,17 @@ namespace osu.Game.Screens.SelectV2 private BeatmapSetOnlineStatusPill statusPill = null!; private DifficultySpectrumDisplay difficultiesDisplay = null!; - [Resolved] - private BeatmapCarousel? carousel { get; set; } - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; [Resolved] private BeatmapManager beatmaps { get; set; } = null!; + public PanelBeatmapSet() + : base(set_x_offset) + { + } + [BackgroundDependencyLoader] private void load() { @@ -56,27 +55,28 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X; Height = HEIGHT; - InternalChild = panel = new CarouselPanelPiece(set_x_offset) + Icon = chevronIcon = new Container { - Action = () => carousel?.Activate(Item!), - Icon = chevronIcon = new Container + Size = new Vector2(22), + Child = new SpriteIcon { - Size = new Vector2(22), - Child = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(12), - X = 1f, - Colour = colourProvider.Background5, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(12), + X = 1f, + Colour = colourProvider.Background5, }, - Background = background = new BeatmapSetPanelBackground - { - RelativeSizeAxes = Axes.Both, - }, - Child = new FillFlowContainer + }; + + Background = background = new BeatmapSetPanelBackground + { + RelativeSizeAxes = Axes.Both, + }; + + Content.Children = new[] + { + new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -132,12 +132,11 @@ namespace osu.Game.Screens.SelectV2 base.LoadComplete(); Expanded.BindValueChanged(_ => onExpanded(), true); - KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); + KeyboardSelected.BindValueChanged(k => KeyboardSelected.Value = k.NewValue, true); } private void onExpanded() { - panel.Active.Value = Expanded.Value; chevronIcon.ResizeWidthTo(Expanded.Value ? 22 : 0f, duration, Easing.OutQuint); chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } @@ -171,20 +170,5 @@ namespace osu.Game.Screens.SelectV2 updateButton.BeatmapSet = null; difficultiesDisplay.BeatmapSet = null; } - - #region ICarouselPanel - - public CarouselItem? Item { get; set; } - public BindableBool Selected { get; } = new BindableBool(); - public BindableBool Expanded { get; } = new BindableBool(); - public BindableBool KeyboardSelected { get; } = new BindableBool(); - - public double DrawYPosition { get; set; } - - public void Activated() - { - } - - #endregion } } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index c858e039ec..c94a337cd9 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -10,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -25,7 +23,7 @@ using osuTK; namespace osu.Game.Screens.SelectV2 { - public partial class PanelBeatmapStandalone : PoolableDrawable, ICarouselPanel + public partial class PanelBeatmapStandalone : PanelBase { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; @@ -35,9 +33,6 @@ namespace osu.Game.Screens.SelectV2 private const float duration = 500; - [Resolved] - private BeatmapCarousel? carousel { get; set; } - [Resolved] private IBindable ruleset { get; set; } = null!; @@ -59,7 +54,6 @@ namespace osu.Game.Screens.SelectV2 private IBindable? starDifficultyBindable; private CancellationTokenSource? starDifficultyCancellationSource; - private CarouselPanelPiece panel = null!; private BeatmapSetPanelBackground background = null!; private OsuSpriteText titleText = null!; @@ -75,6 +69,11 @@ namespace osu.Game.Screens.SelectV2 private OsuSpriteText difficultyName = null!; private OsuSpriteText difficultyAuthor = null!; + public PanelBeatmapStandalone() + : base(standalone_x_offset) + { + } + [BackgroundDependencyLoader] private void load() { @@ -84,107 +83,105 @@ namespace osu.Game.Screens.SelectV2 Width = 1f; Height = HEIGHT; - InternalChild = panel = new CarouselPanelPiece(standalone_x_offset) + Icon = difficultyIcon = new ConstrainedIconContainer { - Action = () => carousel?.Activate(Item!), - Icon = difficultyIcon = new ConstrainedIconContainer + Size = new Vector2(20), + Margin = new MarginPadding { Horizontal = 5f }, + Colour = colourProvider.Background5, + }; + + Background = background = new BeatmapSetPanelBackground + { + RelativeSizeAxes = Axes.Both, + }; + + Content.Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, + Children = new Drawable[] { - Size = new Vector2(20), - Margin = new MarginPadding { Horizontal = 5f }, - Colour = colourProvider.Background5, - }, - Background = background = new BeatmapSetPanelBackground - { - RelativeSizeAxes = Axes.Both, - }, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 }, - Children = new Drawable[] + titleText = new OsuSpriteText { - titleText = new OsuSpriteText + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + artistText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5f }, + Children = new Drawable[] { - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), - Shadow = true, - }, - artistText = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), - Shadow = true, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5f }, - Children = new Drawable[] + updateButton = new UpdateBeatmapSetButton { - updateButton = new UpdateBeatmapSetButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5f, Top = -2f }, - }, - statusPill = new BeatmapSetOnlineStatusPill - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Margin = new MarginPadding { Right = 5f }, - }, - difficultyLine = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small) - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Scale = new Vector2(8f / 9f), - Margin = new MarginPadding { Right = 5f }, - }, - difficultyRank = new TopLocalRank - { - Scale = new Vector2(8f / 11), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5f }, - }, - difficultyKeyCountText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Alpha = 0, - Margin = new MarginPadding { Bottom = 2f }, - }, - difficultyName = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Margin = new MarginPadding { Right = 5f, Bottom = 2f }, - }, - difficultyAuthor = new OsuSpriteText - { - Colour = colourProvider.Content2, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Margin = new MarginPadding { Right = 5f, Bottom = 2f }, - } - } - }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f, Top = -2f }, }, - } + statusPill = new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Margin = new MarginPadding { Right = 5f }, + }, + difficultyLine = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Scale = new Vector2(8f / 9f), + Margin = new MarginPadding { Right = 5f }, + }, + difficultyRank = new TopLocalRank + { + Scale = new Vector2(8f / 11), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5f }, + }, + difficultyKeyCountText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + Margin = new MarginPadding { Bottom = 2f }, + }, + difficultyName = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 5f, Bottom = 2f }, + }, + difficultyAuthor = new OsuSpriteText + { + Colour = colourProvider.Content2, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 5f, Bottom = 2f }, + } + } + }, + }, } - }, + } }; } @@ -203,9 +200,6 @@ namespace osu.Game.Screens.SelectV2 computeStarRating(); updateKeyCount(); }, true); - - Selected.BindValueChanged(s => panel.Active.Value = s.NewValue, true); - KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); } protected override void PrepareForUse() @@ -289,26 +283,9 @@ namespace osu.Game.Screens.SelectV2 { var starDifficulty = starDifficultyBindable?.Value ?? default; - panel.AccentColour = colours.ForStarDifficulty(starDifficulty.Stars); + AccentColour = colours.ForStarDifficulty(starDifficulty.Stars); difficultyIcon.FadeColour(starDifficulty.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); difficultyStarRating.Current.Value = starDifficulty; } - - #region ICarouselPanel - - public CarouselItem? Item { get; set; } - public BindableBool Selected { get; } = new BindableBool(); - public BindableBool Expanded { get; } = new BindableBool(); - public BindableBool KeyboardSelected { get; } = new BindableBool(); - - public double DrawYPosition { get; set; } - - public void Activated() - { - // sets should never be activated. - throw new InvalidOperationException(); - } - - #endregion } } diff --git a/osu.Game/Screens/SelectV2/PanelGroup.cs b/osu.Game/Screens/SelectV2/PanelGroup.cs index cdd0695147..2b4fb9e4a9 100644 --- a/osu.Game/Screens/SelectV2/PanelGroup.cs +++ b/osu.Game/Screens/SelectV2/PanelGroup.cs @@ -3,11 +3,9 @@ using System.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -18,16 +16,12 @@ using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { - public partial class PanelGroup : PoolableDrawable, ICarouselPanel + public partial class PanelGroup : PanelBase { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; private const float duration = 500; - [Resolved] - private BeatmapCarousel? carousel { get; set; } - - private CarouselPanelPiece panel = null!; private Drawable chevronIcon = null!; private OsuSpriteText titleText = null!; @@ -39,57 +33,53 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X; Height = HEIGHT; - InternalChild = panel = new CarouselPanelPiece(0) + Icon = chevronIcon = new SpriteIcon { - Action = () => carousel?.Activate(Item!), - Icon = chevronIcon = new SpriteIcon + AlwaysPresent = true, + Icon = FontAwesome.Solid.ChevronDown, + Size = new Vector2(12), + Margin = new MarginPadding { Horizontal = 5f }, + X = 2f, + Colour = colourProvider.Background3, + }; + Background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Dark1, + }; + AccentColour = colourProvider.Highlight1; + Content.Children = new Drawable[] + { + titleText = new OsuSpriteText { - AlwaysPresent = true, - Icon = FontAwesome.Solid.ChevronDown, - Size = new Vector2(12), - Margin = new MarginPadding { Horizontal = 5f }, - X = 2f, - Colour = colourProvider.Background3, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + X = 10f, }, - Background = new Box + new CircularContainer { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Dark1, - }, - AccentColour = colourProvider.Highlight1, - Children = new Drawable[] - { - titleText = new OsuSpriteText + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 20f }, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - X = 10f, - }, - new CircularContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(50f, 14f), - Margin = new MarginPadding { Right = 20f }, - Masking = true, - Children = new Drawable[] + new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), - // TODO: requires Carousel/CarouselItem-side implementation - Text = "43", - UseFullGlyphHeight = false, - } + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), }, - } + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), + // TODO: requires Carousel/CarouselItem-side implementation + Text = "43", + UseFullGlyphHeight = false, + } + }, } }; } @@ -99,14 +89,10 @@ namespace osu.Game.Screens.SelectV2 base.LoadComplete(); Expanded.BindValueChanged(_ => onExpanded(), true); - KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); } private void onExpanded() { - panel.Active.Value = Expanded.Value; - panel.Flash(); - chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } @@ -124,20 +110,5 @@ namespace osu.Game.Screens.SelectV2 FinishTransforms(true); this.FadeInFromZero(500, Easing.OutQuint); } - - #region ICarouselPanel - - public CarouselItem? Item { get; set; } - public BindableBool Selected { get; } = new BindableBool(); - public BindableBool Expanded { get; } = new BindableBool(); - public BindableBool KeyboardSelected { get; } = new BindableBool(); - - public double DrawYPosition { get; set; } - - public void Activated() - { - } - - #endregion } } diff --git a/osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs b/osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs new file mode 100644 index 0000000000..736a0f71dc --- /dev/null +++ b/osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs @@ -0,0 +1,149 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using osu.Framework.Allocation; +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.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class PanelGroupStarDifficulty : PanelBase + { + public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; + + private const float duration = 500; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private Drawable chevronIcon = null!; + private Box contentBackground = null!; + private StarRatingDisplay starRatingDisplay = null!; + private StarCounter starCounter = null!; + + [BackgroundDependencyLoader] + private void load() + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + RelativeSizeAxes = Axes.X; + Height = HEIGHT; + + Icon = chevronIcon = new SpriteIcon + { + AlwaysPresent = true, + Icon = FontAwesome.Solid.ChevronDown, + Size = new Vector2(12), + Margin = new MarginPadding { Horizontal = 5f }, + X = 2f, + }; + Background = contentBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Dark1, + }; + AccentColour = colourProvider.Highlight1; + Content.Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f, 0f), + Margin = new MarginPadding { Left = 10f }, + Children = new Drawable[] + { + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(8f / 20f), + }, + } + }, + new CircularContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 20f }, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), + // TODO: requires Carousel/CarouselItem-side implementation + Text = "43", + UseFullGlyphHeight = false, + } + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(_ => onExpanded(), true); + } + + private void onExpanded() + { + chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); + chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + Debug.Assert(Item != null); + + int starNumber = (int)((GroupDefinition)Item.Model).Data; + + Color4 colour = starNumber >= 9 ? OsuColour.Gray(0.2f) : colours.ForStarDifficulty(starNumber); + Color4 contentColour = starNumber >= 7 ? colours.Orange1 : colourProvider.Background5; + + AccentColour = colour; + contentBackground.Colour = colour.Darken(0.3f); + + starRatingDisplay.Current.Value = new StarDifficulty(starNumber, 0); + starCounter.Current = starNumber; + + chevronIcon.Colour = contentColour; + starCounter.Colour = contentColour; + + this.FadeInFromZero(500, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/SelectV2/PanelGroupStarDificulty.cs b/osu.Game/Screens/SelectV2/PanelGroupStarDificulty.cs deleted file mode 100644 index 2215e643bd..0000000000 --- a/osu.Game/Screens/SelectV2/PanelGroupStarDificulty.cs +++ /dev/null @@ -1,187 +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.Diagnostics; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.SelectV2 -{ - public partial class PanelGroupStarDificulty : PoolableDrawable, ICarouselPanel - { - public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - - private const float duration = 500; - - [Resolved] - private BeatmapCarousel? carousel { get; set; } - - [Resolved] - private OsuColour colours { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - private CarouselPanelPiece panel = null!; - private Drawable chevronIcon = null!; - private Box contentBackground = null!; - private StarRatingDisplay starRatingDisplay = null!; - private StarCounter starCounter = null!; - - [BackgroundDependencyLoader] - private void load() - { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - RelativeSizeAxes = Axes.X; - Height = HEIGHT; - - InternalChild = panel = new CarouselPanelPiece(0) - { - Action = onAction, - Icon = chevronIcon = new SpriteIcon - { - AlwaysPresent = true, - Icon = FontAwesome.Solid.ChevronDown, - Size = new Vector2(12), - Margin = new MarginPadding { Horizontal = 5f }, - X = 2f, - }, - Background = contentBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Dark1, - }, - AccentColour = colourProvider.Highlight1, - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10f, 0f), - Margin = new MarginPadding { Left = 10f }, - Children = new Drawable[] - { - starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - starCounter = new StarCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(8f / 20f), - }, - } - }, - new CircularContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(50f, 14f), - Margin = new MarginPadding { Right = 20f }, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), - // TODO: requires Carousel/CarouselItem-side implementation - Text = "43", - UseFullGlyphHeight = false, - } - }, - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Expanded.BindValueChanged(_ => onExpanded(), true); - KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true); - } - - private void onExpanded() - { - panel.Active.Value = Expanded.Value; - panel.Flash(); - - chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); - chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); - } - - protected override void PrepareForUse() - { - base.PrepareForUse(); - - Debug.Assert(Item != null); - - int starNumber = (int)((GroupDefinition)Item.Model).Data; - - Color4 colour = starNumber >= 9 ? OsuColour.Gray(0.2f) : colours.ForStarDifficulty(starNumber); - Color4 contentColour = starNumber >= 7 ? colours.Orange1 : colourProvider.Background5; - - panel.AccentColour = colour; - contentBackground.Colour = colour.Darken(0.3f); - - starRatingDisplay.Current.Value = new StarDifficulty(starNumber, 0); - starCounter.Current = starNumber; - - chevronIcon.Colour = contentColour; - starCounter.Colour = contentColour; - - this.FadeInFromZero(500, Easing.OutQuint); - } - - private void onAction() - { - if (carousel != null) - carousel.CurrentSelection = Item!.Model; - } - - #region ICarouselPanel - - public CarouselItem? Item { get; set; } - public BindableBool Selected { get; } = new BindableBool(); - public BindableBool Expanded { get; } = new BindableBool(); - public BindableBool KeyboardSelected { get; } = new BindableBool(); - - public double DrawYPosition { get; set; } - - public void Activated() - { - // sets should never be activated. - throw new InvalidOperationException(); - } - - #endregion - } -} From 5de9584171cfcbc6394e5bc52c547d5ebac4573e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 16:24:04 +0900 Subject: [PATCH 45/92] Move `PanelXOffset` to `init` property rather than ctor Feels better to me. --- osu.Game/Screens/SelectV2/PanelBase.cs | 53 +++++++------------ osu.Game/Screens/SelectV2/PanelBeatmapSet.cs | 6 +-- .../SelectV2/PanelBeatmapStandalone.cs | 6 +-- 3 files changed, 21 insertions(+), 44 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBase.cs b/osu.Game/Screens/SelectV2/PanelBase.cs index d5a087dbb2..9773d93f45 100644 --- a/osu.Game/Screens/SelectV2/PanelBase.cs +++ b/osu.Game/Screens/SelectV2/PanelBase.cs @@ -29,31 +29,25 @@ namespace osu.Game.Screens.SelectV2 private const float duration = 500; - private readonly float panelXOffset; + protected float PanelXOffset { get; init; } - private readonly Box backgroundBorder; - private readonly Box backgroundGradient; - private readonly Box backgroundAccentGradient; - private readonly Container backgroundLayer; - private readonly Container backgroundLayerHorizontalPadding; - private readonly Container backgroundContainer; - private readonly Container iconContainer; - private readonly Box activationFlash; - private readonly Box hoverLayer; + private Box backgroundBorder = null!; + private Box backgroundGradient = null!; + private Box backgroundAccentGradient = null!; + private Container backgroundLayer = null!; + private Container backgroundLayerHorizontalPadding = null!; + private Container backgroundContainer = null!; + private Container iconContainer = null!; + private Box activationFlash = null!; + private Box hoverLayer = null!; - public Container TopLevelContent { get; } + public Container TopLevelContent { get; private set; } = null!; - protected Container Content { get; } + protected Container Content { get; private set; } = null!; - public Drawable Background - { - set => backgroundContainer.Child = value; - } + public Drawable Background { set => backgroundContainer.Child = value; } - public Drawable Icon - { - set => iconContainer.Child = value; - } + public Drawable Icon { set => iconContainer.Child = value; } private Color4? accentColour; @@ -77,10 +71,9 @@ namespace osu.Game.Screens.SelectV2 return inputRectangle.Contains(TopLevelContent.ToLocalSpace(screenSpacePos)); } - protected PanelBase(float panelXOffset = 0) + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - this.panelXOffset = panelXOffset; - RelativeSizeAxes = Axes.Both; InternalChild = TopLevelContent = new Container @@ -147,7 +140,7 @@ namespace osu.Game.Screens.SelectV2 Content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = panelXOffset + corner_radius }, + Padding = new MarginPadding { Right = PanelXOffset + corner_radius }, }, hoverLayer = new Box { @@ -165,11 +158,7 @@ namespace osu.Game.Screens.SelectV2 new HoverSounds(), } }; - } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) - { hoverLayer.Colour = colours.Blue.Opacity(0.1f); backgroundGradient.Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4); } @@ -187,15 +176,11 @@ namespace osu.Game.Screens.SelectV2 protected override bool OnClick(ClickEvent e) { + activationFlash.FadeOutFromOne(500, Easing.OutQuint); carousel?.Activate(Item!); return true; } - public void Flash() - { - activationFlash.FadeOutFromOne(500, Easing.OutQuint); - } - private void updateDisplay() { backgroundLayer.TransformTo(nameof(Padding), backgroundLayer.Padding with { Vertical = Expanded.Value ? 2f : 0f }, duration, Easing.OutQuint); @@ -214,7 +199,7 @@ namespace osu.Game.Screens.SelectV2 private void updateXOffset() { - float x = panelXOffset + active_x_offset + keyboard_active_x_offset + left_edge_x_offset; + float x = PanelXOffset + active_x_offset + keyboard_active_x_offset + left_edge_x_offset; if (Expanded.Value) x -= active_x_offset; diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index 742fe6b6e6..6ac52acac0 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -21,10 +21,6 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - // todo: this should be replaced with information from CarouselItem about how deep is PanelBeatmap in the carousel - // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). - private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. - private const float duration = 500; private BeatmapSetPanelBackground background = null!; @@ -43,8 +39,8 @@ namespace osu.Game.Screens.SelectV2 private BeatmapManager beatmaps { get; set; } = null!; public PanelBeatmapSet() - : base(set_x_offset) { + PanelXOffset = 20f; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index c94a337cd9..89f9df332f 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -27,10 +27,6 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - // todo: this should be replaced with information from CarouselItem about how deep is PanelBeatmap in the carousel - // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). - private const float standalone_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically. - private const float duration = 500; [Resolved] @@ -70,8 +66,8 @@ namespace osu.Game.Screens.SelectV2 private OsuSpriteText difficultyAuthor = null!; public PanelBeatmapStandalone() - : base(standalone_x_offset) { + PanelXOffset = 20; } [BackgroundDependencyLoader] From 644fb29843a9c33b137cf1056dea659b561815b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 16:27:19 +0900 Subject: [PATCH 46/92] Fix input handling not matching latest `master` logic --- osu.Game/Screens/SelectV2/PanelBase.cs | 10 ---------- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 14 +++++++------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBase.cs b/osu.Game/Screens/SelectV2/PanelBase.cs index 9773d93f45..d0499f44cb 100644 --- a/osu.Game/Screens/SelectV2/PanelBase.cs +++ b/osu.Game/Screens/SelectV2/PanelBase.cs @@ -61,16 +61,6 @@ namespace osu.Game.Screens.SelectV2 } } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) - { - var inputRectangle = TopLevelContent.DrawRectangle; - - // Cover potential gaps introduced by the spacing between panels. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); - - return inputRectangle.Contains(TopLevelContent.ToLocalSpace(screenSpacePos)); - } - [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 48d15f6857..69e8e34c40 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -52,6 +52,12 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private IBindable> mods { get; set; } = null!; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { var inputRectangle = DrawRectangle; @@ -60,17 +66,11 @@ namespace osu.Game.Screens.SelectV2 // // Caveat is that for simplicity, we are covering the full spacing, so panels with frontmost depth will have a slightly // larger hit target. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING }); return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); } - [Resolved] - private IBindable ruleset { get; set; } = null!; - - [Resolved] - private IBindable> mods { get; set; } = null!; - [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { From 7e1984452fb7601330dcf9b0b693cdb17d41ca1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 16:32:12 +0900 Subject: [PATCH 47/92] Tidy up remaining common code --- osu.Game/Screens/SelectV2/PanelBase.cs | 12 +++++++++- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 16 ++----------- osu.Game/Screens/SelectV2/PanelBeatmapSet.cs | 10 ++------ .../SelectV2/PanelBeatmapStandalone.cs | 12 ++-------- osu.Game/Screens/SelectV2/PanelGroup.cs | 10 ++------ .../SelectV2/PanelGroupStarDifficulty.cs | 23 +++++++------------ 6 files changed, 27 insertions(+), 56 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBase.cs b/osu.Game/Screens/SelectV2/PanelBase.cs index d0499f44cb..805cbac8eb 100644 --- a/osu.Game/Screens/SelectV2/PanelBase.cs +++ b/osu.Game/Screens/SelectV2/PanelBase.cs @@ -64,7 +64,11 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { - RelativeSizeAxes = Axes.Both; + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + + RelativeSizeAxes = Axes.X; + Height = CarouselItem.DEFAULT_HEIGHT; InternalChild = TopLevelContent = new Container { @@ -161,6 +165,12 @@ namespace osu.Game.Screens.SelectV2 KeyboardSelected.BindValueChanged(_ => updateDisplay(), true); } + protected override void PrepareForUse() + { + base.PrepareForUse(); + this.FadeInFromZero(duration, Easing.OutQuint); + } + [Resolved] private BeatmapCarousel? carousel { get; set; } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 69e8e34c40..dcac460905 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -26,12 +26,6 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - // todo: this should be replaced with information from CarouselItem about how deep is PanelBeatmap in the carousel - // (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set). - private const float difficulty_x_offset = 100f; // constant X offset for beatmap difficulty panels specifically. - - private const float duration = 500; - private StarCounter starCounter = null!; private ConstrainedIconContainer difficultyIcon = null!; private OsuSpriteText keyCountText = null!; @@ -74,11 +68,6 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - - RelativeSizeAxes = Axes.X; - Width = 1f; Height = HEIGHT; Icon = difficultyIcon = new ConstrainedIconContainer @@ -194,9 +183,6 @@ namespace osu.Game.Screens.SelectV2 computeStarRating(); updateKeyCount(); - - FinishTransforms(true); - this.FadeInFromZero(duration, Easing.OutQuint); } protected override void FreeAfterUse() @@ -244,6 +230,8 @@ namespace osu.Game.Screens.SelectV2 private void updateDisplay() { + const float duration = 500; + var starDifficulty = starDifficultyBindable?.Value ?? default; starRatingDisplay.Current.Value = starDifficulty; diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index 6ac52acac0..5c38fe8e04 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - private const float duration = 500; - private BeatmapSetPanelBackground background = null!; private OsuSpriteText titleText = null!; @@ -46,9 +44,6 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load() { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - RelativeSizeAxes = Axes.X; Height = HEIGHT; Icon = chevronIcon = new Container @@ -133,6 +128,8 @@ namespace osu.Game.Screens.SelectV2 private void onExpanded() { + const float duration = 500; + chevronIcon.ResizeWidthTo(Expanded.Value ? 22 : 0f, duration, Easing.OutQuint); chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } @@ -153,9 +150,6 @@ namespace osu.Game.Screens.SelectV2 updateButton.BeatmapSet = beatmapSet; statusPill.Status = beatmapSet.Status; difficultiesDisplay.BeatmapSet = beatmapSet; - - FinishTransforms(true); - this.FadeInFromZero(duration, Easing.OutQuint); } protected override void FreeAfterUse() diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 89f9df332f..231c7274be 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -27,8 +27,6 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f; - private const float duration = 500; - [Resolved] private IBindable ruleset { get; set; } = null!; @@ -73,10 +71,6 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load() { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - RelativeSizeAxes = Axes.X; - Width = 1f; Height = HEIGHT; Icon = difficultyIcon = new ConstrainedIconContainer @@ -224,10 +218,6 @@ namespace osu.Game.Screens.SelectV2 difficultyLine.Show(); computeStarRating(); - - FinishTransforms(true); - - this.FadeInFromZero(duration, Easing.OutQuint); } protected override void FreeAfterUse() @@ -277,6 +267,8 @@ namespace osu.Game.Screens.SelectV2 private void updateDisplay() { + const float duration = 500; + var starDifficulty = starDifficultyBindable?.Value ?? default; AccentColour = colours.ForStarDifficulty(starDifficulty.Stars); diff --git a/osu.Game/Screens/SelectV2/PanelGroup.cs b/osu.Game/Screens/SelectV2/PanelGroup.cs index 2b4fb9e4a9..ecb64f4797 100644 --- a/osu.Game/Screens/SelectV2/PanelGroup.cs +++ b/osu.Game/Screens/SelectV2/PanelGroup.cs @@ -20,17 +20,12 @@ namespace osu.Game.Screens.SelectV2 { public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - private const float duration = 500; - private Drawable chevronIcon = null!; private OsuSpriteText titleText = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - RelativeSizeAxes = Axes.X; Height = HEIGHT; Icon = chevronIcon = new SpriteIcon @@ -93,6 +88,8 @@ namespace osu.Game.Screens.SelectV2 private void onExpanded() { + const float duration = 500; + chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } @@ -106,9 +103,6 @@ namespace osu.Game.Screens.SelectV2 GroupDefinition group = (GroupDefinition)Item.Model; titleText.Text = group.Title; - - FinishTransforms(true); - this.FadeInFromZero(500, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs b/osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs index 736a0f71dc..0dc5a2f365 100644 --- a/osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs +++ b/osu.Game/Screens/SelectV2/PanelGroupStarDifficulty.cs @@ -21,10 +21,6 @@ namespace osu.Game.Screens.SelectV2 { public partial class PanelGroupStarDifficulty : PanelBase { - public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT; - - private const float duration = 500; - [Resolved] private OsuColour colours { get; set; } = null!; @@ -39,10 +35,7 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load() { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - RelativeSizeAxes = Axes.X; - Height = HEIGHT; + Height = PanelGroup.HEIGHT; Icon = chevronIcon = new SpriteIcon { @@ -117,12 +110,6 @@ namespace osu.Game.Screens.SelectV2 Expanded.BindValueChanged(_ => onExpanded(), true); } - private void onExpanded() - { - chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); - chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); - } - protected override void PrepareForUse() { base.PrepareForUse(); @@ -142,8 +129,14 @@ namespace osu.Game.Screens.SelectV2 chevronIcon.Colour = contentColour; starCounter.Colour = contentColour; + } - this.FadeInFromZero(500, Easing.OutQuint); + private void onExpanded() + { + const float duration = 500; + + chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint); + chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); } } } From 8299dfc6f2341f82ac1baeeb40967a5391296de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Feb 2025 10:10:51 +0100 Subject: [PATCH 48/92] Add local guard before scheduled placeholder user set When API is in `RequiresSecondFactorAuth` state, `attemptConnect()` is called over and over in a loop, with no sleeping, which means that the scheduler accumulates hundreds of thousands of these delegates. Sure you could add a sleep in there maybe, but it seems pretty wasteful to have the `localUser.IsDefault` guard *inside* the schedule anyway, so this is what I opted for. --- osu.Game/Online/API/APIAccess.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 88f9b3f242..711866b2aa 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -238,7 +238,8 @@ namespace osu.Game.Online.API /// Whether the connection attempt was successful. private void attemptConnect() { - Scheduler.Add(setPlaceholderLocalUser, false); + if (localUser.IsDefault) + Scheduler.Add(setPlaceholderLocalUser, false); // save the username at this point, if the user requested for it to be. config.SetValue(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); From d6552f00bed2359296df91050062a3786c59493e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Feb 2025 10:14:00 +0100 Subject: [PATCH 49/92] Do not attempt to automatically reconnect if there is no login to use Because it'll fail anyway - there is either no username or no password. The reason why this is important is that the block was also setting API state to `Connecting`. --- osu.Game/Online/API/APIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 711866b2aa..a90fccc1c0 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -244,7 +244,7 @@ namespace osu.Game.Online.API // save the username at this point, if the user requested for it to be. config.SetValue(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); - if (!authentication.HasValidAccessToken) + if (!authentication.HasValidAccessToken && HasLogin) { state.Value = APIState.Connecting; LastLoginError = null; From 930e02300f200388e5398fee1e34f14108f09d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Feb 2025 10:20:38 +0100 Subject: [PATCH 50/92] Do not allow flushed requests to transition API into `Failing` state Flushes are assumed to have already come from a definitive state change (read: disconnection). Allowing the exceptions that come from failing the flushed requests to trigger the `Failing` code paths makes completely incorrect behaviour possible. --- osu.Game/Online/API/APIAccess.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a90fccc1c0..479fc99805 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -253,6 +253,10 @@ namespace osu.Game.Online.API { authentication.AuthenticateWithLogin(ProvidedUsername, password); } + catch (WebRequestFlushedException) + { + return; + } catch (Exception e) { //todo: this fails even on network-related issues. we should probably handle those differently. @@ -313,7 +317,7 @@ namespace osu.Game.Online.API log.Add(@"Login no longer valid"); Logout(); } - else + else if (ex is not WebRequestFlushedException) { state.Value = APIState.Failing; } @@ -494,6 +498,11 @@ namespace osu.Game.Online.API handleWebException(we); return false; } + catch (WebRequestFlushedException wrf) + { + log.Add(wrf.Message); + return false; + } catch (Exception ex) { Logger.Error(ex, "Error occurred while handling an API request."); @@ -575,7 +584,7 @@ namespace osu.Game.Online.API if (failOldRequests) { foreach (var req in oldQueueRequests) - req.Fail(new WebException($@"Request failed from flush operation (state {state.Value})")); + req.Fail(new WebRequestFlushedException(state.Value)); } } } @@ -606,7 +615,11 @@ namespace osu.Game.Online.API return; var friendsReq = new GetFriendsRequest(); - friendsReq.Failure += _ => state.Value = APIState.Failing; + friendsReq.Failure += ex => + { + if (ex is not WebRequestFlushedException) + state.Value = APIState.Failing; + }; friendsReq.Success += res => { var existingFriends = friends.Select(f => f.TargetID).ToHashSet(); @@ -631,6 +644,14 @@ namespace osu.Game.Online.API flushQueue(); cancellationToken.Cancel(); } + + private class WebRequestFlushedException : Exception + { + public WebRequestFlushedException(APIState state) + : base($@"Request failed from flush operation (state {state})") + { + } + } } internal class GuestUser : APIUser From b3aba537b5f081978ea4d349562ec8c02289f737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Feb 2025 11:33:30 +0100 Subject: [PATCH 51/92] Add missing early return As spotted in testing with production. Would cause submission to proceed even if the export did, with an empty archive. --- osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs index 201888e078..f62b793918 100644 --- a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs +++ b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs @@ -249,6 +249,7 @@ namespace osu.Game.Screens.Edit.Submission exportProgressNotification = null; Logger.Log($"Beatmap set submission failed on export: {ex}"); allowExit(); + return; } exportStep.SetCompleted(); From e6174f195cf2fdfedf9c9a054172136e3b5b2efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Feb 2025 12:06:42 +0100 Subject: [PATCH 52/92] Ensure `EditorBeatmap.PerformOnSelection()` marks objects in selection as updated Closes https://github.com/ppy/osu/issues/28791. The reason why nudging was not changing hyperdash state in catch was that `EditorBeatmap.Update()` was not being called on the objects that were being modified, therefore postprocessing was not performed, therefore hyperdash state was not being recomputed. Looking at the usage sites of `EditorBeatmap.PerformOnSelection()`, about two-thirds of callers called `Update()` themselves on the objects they mutated, and the rest didn't. I'd say that's the failure of the abstraction and it should be `PerformOnSelection()`'s responsibility to call `Update()` there. Yes in some of the cases here this will cause extraneous calls that weren't done before, but the method is already heavily disclaimed as 'expensive', so I'd say usability should come first. --- osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs | 6 ------ .../Edit/Compose/Components/EditorBlueprintContainer.cs | 8 +------- .../Edit/Compose/Components/EditorSelectionHandler.cs | 9 --------- osu.Game/Screens/Edit/EditorBeatmap.cs | 5 +++++ 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index be2a5ac144..364324087b 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -62,10 +62,7 @@ namespace osu.Game.Rulesets.Taiko.Edit if (h is not TaikoStrongableHitObject strongable) return; if (strongable.IsStrong != state) - { strongable.IsStrong = state; - EditorBeatmap.Update(strongable); - } }); } @@ -77,10 +74,7 @@ namespace osu.Game.Rulesets.Taiko.Edit EditorBeatmap.PerformOnSelection(h => { if (h is Hit taikoHit) - { taikoHit.Type = state ? HitType.Rim : HitType.Centre; - EditorBeatmap.Update(h); - } }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index e67644baaa..e8de1eaad9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -81,13 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components double offset = result.Time.Value - referenceTime; if (offset != 0) - { - Beatmap.PerformOnSelection(obj => - { - obj.StartTime += offset; - Beatmap.Update(obj); - }); - } + Beatmap.PerformOnSelection(obj => obj.StartTime += offset); } protected override void AddBlueprintFor(HitObject item) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index cd6e25734a..f9e7ef6df8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -355,8 +355,6 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); } - - EditorBeatmap.Update(h); }); } @@ -390,8 +388,6 @@ namespace osu.Game.Screens.Edit.Compose.Components hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList(); } } - - EditorBeatmap.Update(h); }); } @@ -439,8 +435,6 @@ namespace osu.Game.Screens.Edit.Compose.Components node.Add(hitSample); } } - - EditorBeatmap.Update(h); }); } @@ -462,8 +456,6 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Where(s => s.Name != sampleName).ToList(); } - - EditorBeatmap.Update(h); }); } @@ -484,7 +476,6 @@ namespace osu.Game.Screens.Edit.Compose.Components if (comboInfo == null || comboInfo.NewCombo == state) return; comboInfo.NewCombo = state; - EditorBeatmap.Update(h); }); } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 44f9646889..254336e963 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -312,8 +312,13 @@ namespace osu.Game.Screens.Edit return; BeginChange(); + foreach (var h in SelectedHitObjects) + { action(h); + Update(h); + } + EndChange(); } From 7566da8663f8f9f33a87842b7eca5339a0fe43da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Feb 2025 23:52:08 +0900 Subject: [PATCH 53/92] Add sleep to reduce spinning when waiting on two factor auth --- osu.Game/Online/API/APIAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 479fc99805..36712fbdaa 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -190,7 +190,10 @@ namespace osu.Game.Online.API attemptConnect(); if (state.Value != APIState.Online) + { + Thread.Sleep(50); continue; + } } // hard bail if we can't get a valid access token. From 687c9d6e174e6e339f9de9641322c7e67e245834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Feb 2025 12:45:37 +0100 Subject: [PATCH 54/92] Send "notify on discussion replies" setting value in beatmap creation request --- .../Online/API/Requests/PutBeatmapSetRequest.cs | 14 ++++++++++---- .../Edit/Submission/BeatmapSubmissionScreen.cs | 4 ++-- .../Edit/Submission/BeatmapSubmissionSettings.cs | 2 ++ .../Edit/Submission/ScreenSubmissionSettings.cs | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/Requests/PutBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/PutBeatmapSetRequest.cs index fb25749786..ec233b5df8 100644 --- a/osu.Game/Online/API/Requests/PutBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/PutBeatmapSetRequest.cs @@ -11,6 +11,7 @@ using osu.Framework.IO.Network; using osu.Framework.Localisation; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Edit.Submission; namespace osu.Game.Online.API.Requests { @@ -42,22 +43,27 @@ namespace osu.Game.Online.API.Requests [JsonProperty("target")] public BeatmapSubmissionTarget SubmissionTarget { get; init; } + [JsonProperty("notify_on_discussion_replies")] + public bool NotifyOnDiscussionReplies { get; init; } + private PutBeatmapSetRequest() { } - public static PutBeatmapSetRequest CreateNew(uint beatmapCount, BeatmapSubmissionTarget target) => new PutBeatmapSetRequest + public static PutBeatmapSetRequest CreateNew(uint beatmapCount, BeatmapSubmissionSettings settings) => new PutBeatmapSetRequest { BeatmapsToCreate = beatmapCount, - SubmissionTarget = target, + SubmissionTarget = settings.Target.Value, + NotifyOnDiscussionReplies = settings.NotifyOnDiscussionReplies.Value, }; - public static PutBeatmapSetRequest UpdateExisting(uint beatmapSetId, IEnumerable beatmapsToKeep, uint beatmapsToCreate, BeatmapSubmissionTarget target) => new PutBeatmapSetRequest + public static PutBeatmapSetRequest UpdateExisting(uint beatmapSetId, IEnumerable beatmapsToKeep, uint beatmapsToCreate, BeatmapSubmissionSettings settings) => new PutBeatmapSetRequest { BeatmapSetID = beatmapSetId, BeatmapsToKeep = beatmapsToKeep.ToArray(), BeatmapsToCreate = beatmapsToCreate, - SubmissionTarget = target, + SubmissionTarget = settings.Target.Value, + NotifyOnDiscussionReplies = settings.NotifyOnDiscussionReplies.Value, }; protected override WebRequest CreateWebRequest() diff --git a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs index f62b793918..66139bacec 100644 --- a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs +++ b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs @@ -192,8 +192,8 @@ namespace osu.Game.Screens.Edit.Submission (uint)Beatmap.Value.BeatmapSetInfo.OnlineID, Beatmap.Value.BeatmapSetInfo.Beatmaps.Where(b => b.OnlineID > 0).Select(b => (uint)b.OnlineID).ToArray(), (uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count(b => b.OnlineID <= 0), - settings.Target.Value) - : PutBeatmapSetRequest.CreateNew((uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, settings.Target.Value); + settings) + : PutBeatmapSetRequest.CreateNew((uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, settings); createRequest.Success += async response => { diff --git a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionSettings.cs b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionSettings.cs index 359dc11f39..8cccc339a6 100644 --- a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionSettings.cs +++ b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionSettings.cs @@ -9,5 +9,7 @@ namespace osu.Game.Screens.Edit.Submission public class BeatmapSubmissionSettings { public Bindable Target { get; } = new Bindable(); + + public Bindable NotifyOnDiscussionReplies { get; } = new Bindable(); } } diff --git a/osu.Game/Screens/Edit/Submission/ScreenSubmissionSettings.cs b/osu.Game/Screens/Edit/Submission/ScreenSubmissionSettings.cs index 08b4d9f712..969105b5c6 100644 --- a/osu.Game/Screens/Edit/Submission/ScreenSubmissionSettings.cs +++ b/osu.Game/Screens/Edit/Submission/ScreenSubmissionSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Submission [BackgroundDependencyLoader] private void load(OsuConfigManager configManager, OsuColour colours, BeatmapSubmissionSettings settings) { - configManager.BindWith(OsuSetting.EditorSubmissionNotifyOnDiscussionReplies, notifyOnDiscussionReplies); + configManager.BindWith(OsuSetting.EditorSubmissionNotifyOnDiscussionReplies, settings.NotifyOnDiscussionReplies); configManager.BindWith(OsuSetting.EditorSubmissionLoadInBrowserAfterSubmission, loadInBrowserAfterSubmission); Content.Add(new FillFlowContainer @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Submission new FormCheckBox { Caption = BeatmapSubmissionStrings.NotifyOnDiscussionReplies, - Current = notifyOnDiscussionReplies, + Current = settings.NotifyOnDiscussionReplies, }, new FormCheckBox { From aa9e1ac8b4bf0154dff870269221d49e7e1c98d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Feb 2025 12:46:04 +0100 Subject: [PATCH 55/92] Specify endpoint for production instance of beatmap submission service --- osu.Game/Online/ProductionEndpointConfiguration.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index 6e06abbeed..20583c8c7e 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -13,6 +13,7 @@ namespace osu.Game.Online SpectatorUrl = "https://spectator.ppy.sh/spectator"; MultiplayerUrl = "https://spectator.ppy.sh/multiplayer"; MetadataUrl = "https://spectator.ppy.sh/metadata"; + BeatmapSubmissionServiceUrl = "https://bss.ppy.sh"; } } } From f9d91431fd0e4eaf4c26d8125b078cdd2ec23c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Feb 2025 15:13:48 +0100 Subject: [PATCH 56/92] Fix multiplayer spectator not working with freestyle It's no longer possible to just assume that using the ambient `WorkingBeatmap` is gonna work. Bit dodgy but seems to work and also I'd hope that `WorkingBeatmapCache` makes this not overly taxing. If there are concerns this can probably be an async load or something. --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 1b03452df7..2a40021ee0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public Score? Score { get; private set; } [Resolved] - private IBindable beatmap { get; set; } = null!; + private BeatmapManager beatmapManager { get; set; } = null!; private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments(); private readonly BindableDouble volumeAdjustment = new BindableDouble(); @@ -89,7 +89,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; - gameplayContent.Child = new PlayerIsolationContainer(beatmap.Value, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + var workingBeatmap = beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo); + workingBeatmap.LoadTrack(); + gameplayContent.Child = new PlayerIsolationContainer(workingBeatmap, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack From a274b9a1fd9f59200061f7fe794de3b8c112e0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Feb 2025 15:24:58 +0100 Subject: [PATCH 57/92] Fix test --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 0a3d48828e..bd483f0fa1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -372,7 +372,8 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(getPlayerIds(4), 300); - AddUntilStep("wait for correct track speed", () => Beatmap.Value.Track.Rate, () => Is.EqualTo(1.5)); + AddUntilStep("wait for correct track speed", + () => this.ChildrenOfType().All(player => player.ClockAdjustmentsFromMods.AggregateTempo.Value == 1.5)); } [Test] From 092d80cf1b330b21ad7a10a09f25e9336999f523 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 19 Feb 2025 10:20:04 -0500 Subject: [PATCH 58/92] Fix `PanelBeatmapStandalone` not handling selection state --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 1 - osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index dcac460905..b27e5cae14 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -165,7 +165,6 @@ namespace osu.Game.Screens.SelectV2 }, true); Selected.BindValueChanged(s => Expanded.Value = s.NewValue, true); - KeyboardSelected.BindValueChanged(k => KeyboardSelected.Value = k.NewValue, true); } protected override void PrepareForUse() diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 231c7274be..948311a86e 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -190,6 +190,8 @@ namespace osu.Game.Screens.SelectV2 computeStarRating(); updateKeyCount(); }, true); + + Selected.BindValueChanged(s => Expanded.Value = s.NewValue, true); } protected override void PrepareForUse() From e91706f41843b48020f514c42c42ed446a565f83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Feb 2025 14:26:33 +0900 Subject: [PATCH 59/92] Update framework --- 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 f4d49763ab..7dfe2f9d1f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 0d95dfbd06..a40bc145ff 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 4da3752f956d2d68dbc263969d81478a5577536d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Feb 2025 14:42:26 +0900 Subject: [PATCH 60/92] Update flag test resources in line with web rename --- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 2 +- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index a4a9816337..2972f69cba 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -351,7 +351,7 @@ namespace osu.Game.Tests.Visual.Online Id = 1, Name = "Collective Wangs", ShortName = "WANG", - FlagUrl = "https://assets.ppy.sh/teams/logo/1/wanglogo.jpg", + FlagUrl = "https://assets.ppy.sh/teams/flag/1/wanglogo.jpg", } }; } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index d73fd5ab22..3e3fe03329 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay { Name = "Collective Wangs", ShortName = "WANG", - FlagUrl = "https://assets.ppy.sh/teams/logo/1/wanglogo.jpg", + FlagUrl = "https://assets.ppy.sh/teams/flag/1/wanglogo.jpg", } : null, }) From 1c53d93a8f3cf68888e74919ee75639fe70ffe05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Feb 2025 15:32:47 +0900 Subject: [PATCH 61/92] Add disposal and pre-check before reloading audio track --- .../OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 2a40021ee0..31bd711ade 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; 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; @@ -60,6 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; private OsuScreenStack? stack; + private Track? loadedTrack; public PlayerArea(int userId, SpectatorPlayerClock clock) { @@ -90,7 +92,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; var workingBeatmap = beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo); - workingBeatmap.LoadTrack(); + if (!workingBeatmap.TrackLoaded) + loadedTrack = workingBeatmap.LoadTrack(); gameplayContent.Child = new PlayerIsolationContainer(workingBeatmap, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, @@ -129,6 +132,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + loadedTrack?.Dispose(); + } + /// /// Isolates each player instance from the game-wide ruleset/beatmap/mods (to allow for different players having different settings). /// From 81b4f0d8caf176aa070846ddf79f54346803fa2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Feb 2025 15:49:40 +0900 Subject: [PATCH 62/92] Add comments regarding jank --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 31bd711ade..7e4aae99da 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -91,9 +91,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; + // Required for freestyle, where each player may be playing a different beatmap. var workingBeatmap = beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo); + + // Required to avoid crashes, but we really don't want to be doing this if we can avoid it. + // If we get to fixing this, we will want to investigate every access to `Track` in gameplay. if (!workingBeatmap.TrackLoaded) loadedTrack = workingBeatmap.LoadTrack(); + gameplayContent.Child = new PlayerIsolationContainer(workingBeatmap, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, From d8bba16809a8f55899afc843fe0010c43b0acc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Feb 2025 14:38:54 +0100 Subject: [PATCH 63/92] Update framework Pulls in fix for https://github.com/ppy/osu/issues/31956. --- 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 7dfe2f9d1f..d49acd7b27 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index a40bc145ff..5ca49e80f6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 1e3d5d7d8150c916af4a059791f1d3c4b5a6f5a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Feb 2025 23:05:43 +0900 Subject: [PATCH 64/92] Remove left-over debug code --- osu.Game/Screens/Play/KiaiGameplayFountains.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 19a9c2b6e5..f7d96dd10f 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -55,7 +55,6 @@ namespace osu.Game.Screens.Play if (EffectPoint.KiaiMode && !isTriggered) { - Logger.Log("shooting"); bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - EffectPoint.Time) < 500; if (isNearEffectPoint) Shoot(); From b4d270045b5fa3840c726add260933d145a78b9f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 20 Feb 2025 09:29:18 -0500 Subject: [PATCH 65/92] Publicise base draw size property --- osu.Android/OsuGameAndroid.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.iOS/OsuGameIOS.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index e725f9245f..932fc8454e 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -21,7 +21,7 @@ namespace osu.Android [Cached] private readonly OsuGameActivity gameActivity; - protected override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth); + public override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth); public OsuGameAndroid(OsuGameActivity activity) : base(null) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d379392a7d..d23d27c89e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -818,7 +818,7 @@ namespace osu.Game /// Adjust the globally applied in every . /// Useful for changing how the game handles different aspect ratios. /// - protected internal virtual Vector2 ScalingContainerTargetDrawSize { get; } = new Vector2(1024, 768); + public virtual Vector2 ScalingContainerTargetDrawSize { get; } = new Vector2(1024, 768); protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 883e89e38a..96b8fb9804 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -23,7 +23,7 @@ namespace osu.iOS public override bool HideUnlicensedContent => true; - protected override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth); + public override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth); public OsuGameIOS(AppDelegate appDelegate) { From 4f4d2b3b3fdd24a6ab3d0a8388e6afd729806483 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Feb 2025 23:42:32 +0900 Subject: [PATCH 66/92] Fix results screen applause playing too loud during multiplayer spectating --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 5 +++++ osu.Game/Screens/Ranking/ResultsScreen.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 7e4aae99da..393d34bc1a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -9,6 +9,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -128,8 +129,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate get => mute; set { + if (mute == value) + return; + mute = value; volumeAdjustment.Value = value ? 0 : 1; + Logger.Log($"{(mute ? "muting" : "unmuting")} player {UserId}"); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index b10684b22e..fe0d805cee 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -317,6 +317,8 @@ namespace osu.Game.Screens.Ranking if (!this.IsCurrentScreen() || s != rankApplauseSound) return; + AddInternal(rankApplauseSound); + rankApplauseSound.VolumeTo(applause_volume); rankApplauseSound.Play(); }); From 7dc5ad2f0e21c85a66f925abf5133d741fcdf937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Feb 2025 15:44:30 +0100 Subject: [PATCH 67/92] Adjust handling of team flags with non-matching aspect ratio to match web --- .../Components/DrawableTeamFlag.cs | 19 ++++++++++++++----- .../Users/Drawables/UpdateableTeamFlag.cs | 11 ++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index aef854bb8d..90638a7758 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -6,6 +6,7 @@ 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.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Tournament.Models; @@ -35,12 +36,20 @@ namespace osu.Game.Tournament.Components Size = new Vector2(75, 54); Masking = true; CornerRadius = 5; - Child = flagSprite = new Sprite + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.FromHex("333"), + }, + flagSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit + }, }; (flag = team.FlagName.GetBoundCopy()).BindValueChanged(_ => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true); diff --git a/osu.Game/Users/Drawables/UpdateableTeamFlag.cs b/osu.Game/Users/Drawables/UpdateableTeamFlag.cs index 9c2bbb7e3e..2fcec66aa7 100644 --- a/osu.Game/Users/Drawables/UpdateableTeamFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableTeamFlag.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; @@ -75,10 +76,18 @@ namespace osu.Game.Users.Drawables InternalChildren = new Drawable[] { new HoverClickSounds(), + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.FromHex("333"), + }, new Sprite { RelativeSizeAxes = Axes.Both, - Texture = textures.Get(team.FlagUrl) + Texture = textures.Get(team.FlagUrl), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, } }; } From a75ec75a8fa5e44fede74c4f1c4e275e6f2abee5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Feb 2025 23:48:21 +0900 Subject: [PATCH 68/92] Fix using --- osu.Game/Screens/Play/KiaiGameplayFountains.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index f7d96dd10f..d4e61dc5a0 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Containers; From 440a776bd7c9edcf935b2da3d1bc07c65fc71663 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 20 Feb 2025 09:29:28 -0500 Subject: [PATCH 69/92] Scale catch down to remain playable on mobile --- .../UI/CatchPlayfieldAdjustmentContainer.cs | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 74dfa6c1fd..3b9cca8ef0 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.UI; @@ -15,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override Container Content => content; private readonly Container content; + private readonly Container scaleContainer; + public CatchPlayfieldAdjustmentContainer() { const float base_game_width = 1024f; @@ -26,30 +29,49 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.Centre; Origin = Anchor.Centre; - InternalChild = new Container + InternalChild = scaleContainer = new Container { - // This container limits vertical visibility of the playfield to ensure fairness between wide and tall resolutions (i.e. tall resolutions should not see more fruits). - // Note that the container still extends across the screen horizontally, so that hit explosions at the sides of the playfield do not get cut off. - Name = "Visible area", Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = base_game_height + extra_bottom_space, - Y = extra_bottom_space / 2, - Masking = true, + RelativeSizeAxes = Axes.Both, Child = new Container { - Name = "Playable area", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - // playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable. - Y = base_game_height * ((1 - playfield_size_adjust) / 4 * 3), - Size = new Vector2(base_game_width, base_game_height) * playfield_size_adjust, - Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both } - }, + // This container limits vertical visibility of the playfield to ensure fairness between wide and tall resolutions (i.e. tall resolutions should not see more fruits). + // Note that the container still extends across the screen horizontally, so that hit explosions at the sides of the playfield do not get cut off. + Name = "Visible area", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = base_game_height + extra_bottom_space, + Y = extra_bottom_space / 2, + Masking = true, + Child = new Container + { + Name = "Playable area", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + // playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable. + Y = base_game_height * ((1 - playfield_size_adjust) / 4 * 3), + Size = new Vector2(base_game_width, base_game_height) * playfield_size_adjust, + Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both } + }, + } }; } + [BackgroundDependencyLoader] + private void load(OsuGame? osuGame) + { + if (osuGame != null) + { + // on mobile platforms where the base aspect ratio is wider, the catch playfield + // needs to be scaled down to remain playable. + const float base_aspect_ratio = 1024f / 768f; + float aspectRatio = osuGame.ScalingContainerTargetDrawSize.X / osuGame.ScalingContainerTargetDrawSize.Y; + scaleContainer.Scale = new Vector2(base_aspect_ratio / aspectRatio); + } + } + /// /// A which scales its content relative to a target width. /// From 7bd5b745e923ae3eea737c3dd13a43f975832cbb Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 20 Feb 2025 09:30:14 -0500 Subject: [PATCH 70/92] Scale taiko down to remain playable --- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index c67f61052c..6a9e5789de 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Taiko.Beatmaps; @@ -19,6 +21,9 @@ namespace osu.Game.Rulesets.Taiko.UI public readonly IBindable LockPlayfieldAspectRange = new BindableBool(true); + [Resolved] + private OsuGame? osuGame { get; set; } + public TaikoPlayfieldAdjustmentContainer() { RelativeSizeAxes = Axes.X; @@ -56,6 +61,18 @@ namespace osu.Game.Rulesets.Taiko.UI relativeHeight = Math.Min(relativeHeight, 1f / 3f); Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f)); + + // on mobile platforms where the base aspect ratio is wider, the taiko playfield + // needs to be scaled down to remain playable. + if (RuntimeInfo.IsMobile && osuGame != null) + { + const float base_aspect_ratio = 1024f / 768f; + float gameAspectRatio = osuGame.ScalingContainerTargetDrawSize.X / osuGame.ScalingContainerTargetDrawSize.Y; + // this magic scale is unexplainable, but required so the playfield doesn't become too zoomed out as the aspect ratio increases. + const float magic_scale = 1.25f; + Scale *= magic_scale * new Vector2(base_aspect_ratio / gameAspectRatio); + } + Width = 1 / Scale.X; } From b1112623dca15dfcac3995d3ac289be0ccb96840 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 20 Feb 2025 09:29:53 -0500 Subject: [PATCH 71/92] Fix taiko touch controls sizing logic --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 0b7f6f621a..53d129e7ca 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -59,11 +59,10 @@ namespace osu.Game.Rulesets.Taiko.UI { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 350, + RelativeSizeAxes = Axes.Both, + Height = 0.45f, Y = 20, Masking = true, - FillMode = FillMode.Fit, Children = new Drawable[] { mainContent = new Container From 49c192b173640ddae1543a23eff4b6059f51f250 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 21 Feb 2025 16:19:05 +0900 Subject: [PATCH 72/92] Fix wrong beatmap attributes in multiplayer spectate --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 393d34bc1a..b8f0a67a46 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -154,12 +154,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private partial class PlayerIsolationContainer : Container { [Cached] + [Cached(typeof(IBindable))] private readonly Bindable ruleset = new Bindable(); [Cached] + [Cached(typeof(IBindable))] private readonly Bindable beatmap = new Bindable(); [Cached] + [Cached(typeof(IBindable>))] private readonly Bindable> mods = new Bindable>(); public PlayerIsolationContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods) From f868f03e1b75556418c8cfd6576781c3324d3fd7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 21 Feb 2025 16:38:55 +0900 Subject: [PATCH 73/92] Fix host change sounds playing when exiting multiplayer rooms --- .../Online/Multiplayer/MultiplayerClient.cs | 6 +++++ .../Multiplayer/MultiplayerRoomSounds.cs | 27 ++++++------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 97161cce48..2d445ea25a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -51,6 +51,11 @@ namespace osu.Game.Online.Multiplayer /// public event Action? UserKicked; + /// + /// Invoked when the room's host is changed. + /// + public event Action? HostChanged; + /// /// Invoked when a new item is added to the playlist. /// @@ -531,6 +536,7 @@ namespace osu.Game.Online.Multiplayer Room.Host = user; APIRoom.Host = user?.User; + HostChanged?.Invoke(user); RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs index d53e485c86..cdf4e96bad 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -20,7 +19,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private Sample? userJoinedSample; private Sample? userLeftSample; private Sample? userKickedSample; - private MultiplayerRoomUser? host; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -35,25 +33,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - client.RoomUpdated += onRoomUpdated; client.UserJoined += onUserJoined; client.UserLeft += onUserLeft; client.UserKicked += onUserKicked; - updateState(); - } - - private void onRoomUpdated() => Scheduler.AddOnce(updateState); - - private void updateState() - { - if (EqualityComparer.Default.Equals(host, client.Room?.Host)) - return; - - // only play sound when the host changes from an already-existing host. - if (host != null) - Scheduler.AddOnce(() => hostChangedSample?.Play()); - - host = client.Room?.Host; + client.HostChanged += onHostChanged; } private void onUserJoined(MultiplayerRoomUser user) @@ -65,16 +48,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onUserKicked(MultiplayerRoomUser user) => Scheduler.AddOnce(() => userKickedSample?.Play()); + private void onHostChanged(MultiplayerRoomUser? host) + { + if (host != null) + Scheduler.AddOnce(() => hostChangedSample?.Play()); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (client.IsNotNull()) { - client.RoomUpdated -= onRoomUpdated; client.UserJoined -= onUserJoined; client.UserLeft -= onUserLeft; client.UserKicked -= onUserKicked; + client.HostChanged -= onHostChanged; } } } From a690b0bae993f06edc45fabc6ea2b5153219cc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 21 Feb 2025 12:05:23 +0100 Subject: [PATCH 74/92] Adjust rounding tolerance in distance snap grid ring colour logic --- osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 88e28df8e3..8322c67def 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -159,7 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // in case 2, we want *flooring* to occur, to prevent a possible off-by-one // because of the rounding snapping forward by a chunk of time significantly too high to be considered a rounding error. // the tolerance margin chosen here is arbitrary and can be adjusted if more cases of this are found. - if (Precision.DefinitelyBigger(beatIndex, fractionalBeatIndex, 0.005)) + if (Precision.DefinitelyBigger(beatIndex, fractionalBeatIndex, 0.01)) beatIndex = (int)Math.Floor(fractionalBeatIndex); var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours); From de78518fea14b4c12c5a2db4bc74d65335f05521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 21 Feb 2025 12:52:59 +0100 Subject: [PATCH 75/92] Fix "use current distance snap" button incorrectly factoring in last object with velocity Closes https://github.com/ppy/osu/issues/32003. --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs | 6 +++++- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index 3c0889d027..45ce3206d2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -1,10 +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 System.Linq; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -14,7 +16,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { - float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime); + var lastObjectWithVelocity = EditorBeatmap.HitObjects.TakeWhile(ho => ho != after).OfType().LastOrDefault(); + + float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime, lastObjectWithVelocity); float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position); return actualDistance / expectedDistance; diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index d0b279f201..4129a6fb2c 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Edit private EditorClock editorClock { get; set; } = null!; [Resolved] - private EditorBeatmap editorBeatmap { get; set; } = null!; + protected EditorBeatmap EditorBeatmap { get; private set; } = null!; [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } = null!; @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Edit } }); - DistanceSpacingMultiplier.Value = editorBeatmap.DistanceSpacing; + DistanceSpacingMultiplier.Value = EditorBeatmap.DistanceSpacing; DistanceSpacingMultiplier.BindValueChanged(multiplier => { distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})"; @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Edit if (multiplier.NewValue != multiplier.OldValue) onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier)); - editorBeatmap.DistanceSpacing = multiplier.NewValue; + EditorBeatmap.DistanceSpacing = multiplier.NewValue; }, true); DistanceSpacingMultiplier.BindDisabledChanged(disabled => distanceSpacingSlider.Alpha = disabled ? 0 : 1, true); @@ -267,7 +267,7 @@ namespace osu.Game.Rulesets.Edit public virtual float GetBeatSnapDistance(IHasSliderVelocity? withVelocity = null) { - return (float)(100 * (withVelocity?.SliderVelocityMultiplier ?? 1) * editorBeatmap.Difficulty.SliderMultiplier * 1 + return (float)(100 * (withVelocity?.SliderVelocityMultiplier ?? 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 / beatSnapProvider.BeatDivisor); } From c77fed637c89aab0e6374c307b4935fe1d6f9097 Mon Sep 17 00:00:00 2001 From: ziv_vy <134942175+ziv-vy@users.noreply.github.com> Date: Sun, 23 Feb 2025 01:01:39 +0200 Subject: [PATCH 76/92] Update MouseSettingsStrings.cs CAPITALISED ONE GODDAMN LETTER --- osu.Game/Localisation/MouseSettingsStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index e61af07364..9609c2dd90 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -25,9 +25,9 @@ namespace osu.Game.Localisation public static LocalisableString HighPrecisionMouse => new TranslatableString(getKey(@"high_precision_mouse"), @"High precision mouse"); /// - /// "Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as "Raw Input"." + /// "Attempts to bypass any operation system mouse acceleration. On Windows, this is equivalent to what used to be known as "Raw Input"." /// - public static LocalisableString HighPrecisionMouseTooltip => new TranslatableString(getKey(@"high_precision_mouse_tooltip"), @"Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as ""Raw Input""."); + public static LocalisableString HighPrecisionMouseTooltip => new TranslatableString(getKey(@"high_precision_mouse_tooltip"), @"Attempts to bypass any operation system mouse acceleration. On Windows, this is equivalent to what used to be known as ""Raw Input""."); /// /// "Confine mouse cursor to window" From d95f31dc5af463423a72981f4328fbd0f0b6c654 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 22 Feb 2025 15:21:54 -0800 Subject: [PATCH 77/92] Also fix operating system terminology --- osu.Game/Localisation/MouseSettingsStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index 9609c2dd90..c92c3b6ddc 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -25,9 +25,9 @@ namespace osu.Game.Localisation public static LocalisableString HighPrecisionMouse => new TranslatableString(getKey(@"high_precision_mouse"), @"High precision mouse"); /// - /// "Attempts to bypass any operation system mouse acceleration. On Windows, this is equivalent to what used to be known as "Raw Input"." + /// "Attempts to bypass any operating system mouse acceleration. On Windows, this is equivalent to what used to be known as "Raw Input"." /// - public static LocalisableString HighPrecisionMouseTooltip => new TranslatableString(getKey(@"high_precision_mouse_tooltip"), @"Attempts to bypass any operation system mouse acceleration. On Windows, this is equivalent to what used to be known as ""Raw Input""."); + public static LocalisableString HighPrecisionMouseTooltip => new TranslatableString(getKey(@"high_precision_mouse_tooltip"), @"Attempts to bypass any operating system mouse acceleration. On Windows, this is equivalent to what used to be known as ""Raw Input""."); /// /// "Confine mouse cursor to window" From f4b427ee66bd169ab7270f9a4ef8f467d4ac4572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 09:15:20 +0100 Subject: [PATCH 78/92] Add failing test case --- .../TestSceneModDifficultyAdjustSettings.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index b40d0b10d2..30470c9c17 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { @@ -220,6 +221,29 @@ namespace osu.Game.Tests.Visual.UserInterface checkBindableAtValue("Circle Size", null); } + [Test] + public void TestResetToDefaultViaDoubleClickingNub() + { + setBeatmapWithDifficultyParameters(5); + + setSliderValue("Circle Size", 3); + setExtendedLimits(true); + + checkSliderAtValue("Circle Size", 3); + checkBindableAtValue("Circle Size", 3); + + AddStep("double click circle size nub", () => + { + var nub = this.ChildrenOfType.SliderNub>().First(); + InputManager.MoveMouseTo(nub); + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + + checkSliderAtValue("Circle Size", 5); + checkBindableAtValue("Circle Size", null); + } + [Test] public void TestModSettingChangeTracker() { From d8cb3b68d3ea90268bb142a2ef6e1de782f2040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 09:34:52 +0100 Subject: [PATCH 79/92] Add "Team" channel type The lack of this bricks chat completely due to newtonsoft deserialisation errors: 2025-02-24 08:32:58 [verbose]: Processing response from https://dev.ppy.sh/api/v2/chat/updates failed with Newtonsoft.Json.JsonSerializationException: Error converting value "TEAM" to type 'osu.Game.Online.Chat.ChannelType'. Path 'presence[39].type', line 1, position 13765. 2025-02-24 08:32:58 [verbose]: ---> System.ArgumentException: Requested value 'TEAM' was not found. 2025-02-24 08:32:58 [verbose]: at Newtonsoft.Json.Utilities.EnumUtils.ParseEnum(Type enumType, NamingStrategy namingStrategy, String value, Boolean disallowNumber) 2025-02-24 08:32:58 [verbose]: at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType) --- osu.Game/Online/Chat/ChannelType.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/ChannelType.cs b/osu.Game/Online/Chat/ChannelType.cs index bd628e90c4..4fb890c2cc 100644 --- a/osu.Game/Online/Chat/ChannelType.cs +++ b/osu.Game/Online/Chat/ChannelType.cs @@ -14,5 +14,6 @@ namespace osu.Game.Online.Chat Group, System, Announce, + Team, } } From be8ec759488e3bb5e5479341c8de400a80f3e9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 09:39:27 +0100 Subject: [PATCH 80/92] Display team chat channel in separate group --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index f027888962..6e874e4ed8 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Chat.ChannelList public ChannelGroup AnnounceChannelGroup { get; private set; } = null!; public ChannelGroup PublicChannelGroup { get; private set; } = null!; + public ChannelGroup TeamChannelGroup { get; private set; } = null!; public ChannelGroup PrivateChannelGroup { get; private set; } = null!; private OsuScrollContainer scroll = null!; @@ -82,6 +83,7 @@ namespace osu.Game.Overlays.Chat.ChannelList AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), selector = new ChannelListItem(ChannelListingChannel), + TeamChannelGroup = new ChannelGroup("TEAM", false), // TODO: replace with osu-web localisable string once available PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), }, }, @@ -156,6 +158,9 @@ namespace osu.Game.Overlays.Chat.ChannelList case ChannelType.Announce: return AnnounceChannelGroup; + case ChannelType.Team: + return TeamChannelGroup; + default: return PublicChannelGroup; } From 194f05d2588fa7f23287b85c3968219102e33a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 09:43:46 +0100 Subject: [PATCH 81/92] Add icons to chat channel group headers Matches web in appearance. Cross-reference: https://github.com/ppy/osu-web/blob/3c9e99eaf4bd9e73d2712f60d67f5bc95f9dfe2b/resources/js/chat/conversation-list.tsx#L13-L19 --- .../Overlays/Chat/ChannelList/ChannelList.cs | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 6e874e4ed8..ae68c9c82e 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Testing; @@ -80,11 +81,12 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, } }, - AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), - PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), + // cross-reference for icons: https://github.com/ppy/osu-web/blob/3c9e99eaf4bd9e73d2712f60d67f5bc95f9dfe2b/resources/js/chat/conversation-list.tsx#L13-L19 + AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), FontAwesome.Solid.Bullhorn, false), + PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), FontAwesome.Solid.Comments, false), selector = new ChannelListItem(ChannelListingChannel), - TeamChannelGroup = new ChannelGroup("TEAM", false), // TODO: replace with osu-web localisable string once available - PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), + TeamChannelGroup = new ChannelGroup("TEAM", FontAwesome.Solid.Users, false), // TODO: replace with osu-web localisable string once available + PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), FontAwesome.Solid.Envelope, true), }, }, }, @@ -179,7 +181,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private readonly bool sortByRecent; public readonly ChannelListItemFlow ItemFlow; - public ChannelGroup(LocalisableString label, bool sortByRecent) + public ChannelGroup(LocalisableString label, IconUsage icon, bool sortByRecent) { this.sortByRecent = sortByRecent; Direction = FillDirection.Vertical; @@ -189,11 +191,26 @@ namespace osu.Game.Overlays.Chat.ChannelList Children = new Drawable[] { - new OsuSpriteText + new FillFlowContainer { - Text = label, - Margin = new MarginPadding { Left = 18, Bottom = 5 }, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = label, + Margin = new MarginPadding { Left = 18, Bottom = 5 }, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + }, + new SpriteIcon + { + Icon = icon, + Size = new Vector2(12), + }, + } }, ItemFlow = new ChannelListItemFlow(sortByRecent) { From 4ac4b308e10d041dec5960f808ce2d295171f3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 09:48:03 +0100 Subject: [PATCH 82/92] Add visual test coverage of team channels --- .../Visual/Online/TestSceneChannelList.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index 5f77e084da..8f8cf036f1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -115,6 +115,12 @@ namespace osu.Game.Tests.Visual.Online channelList.AddChannel(createRandomPrivateChannel()); }); + AddStep("Add Team Channels", () => + { + for (int i = 0; i < 10; i++) + channelList.AddChannel(createRandomTeamChannel()); + }); + AddStep("Add Announce Channels", () => { for (int i = 0; i < 2; i++) @@ -189,5 +195,16 @@ namespace osu.Game.Tests.Visual.Online Id = id, }; } + + private Channel createRandomTeamChannel() + { + int id = TestResources.GetNextTestID(); + return new Channel + { + Name = $"Team {id}", + Type = ChannelType.Team, + Id = id, + }; + } } } From 0312467c8840067054c6326d9da821329b7bf01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 12:30:37 +0100 Subject: [PATCH 83/92] Fix hash comparison being case sensitive when choosing files for partial beatmap submission Noticed when investigating https://github.com/ppy/osu/issues/32059, and also a likely cause for user reports like https://discord.com/channels/188630481301012481/1097318920991559880/1342962553101357066. Honestly I have no solid defence, Your Honour. I guess this just must not have been tested on the client side, only relied on server-side testing. --- osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs index 66139bacec..13981bcb69 100644 --- a/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs +++ b/osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Edit.Submission continue; } - if (localHash != onlineHash) + if (!localHash.Equals(onlineHash, StringComparison.OrdinalIgnoreCase)) filesToUpdate.Add(filename); } From 41db3c1501bbfc40f1eb9952fa9d332319ff347b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 14:30:55 +0100 Subject: [PATCH 84/92] Fix taiko swell ending samples playing at results sometimes Closes https://github.com/ppy/osu/issues/32052. Sooooo... this is going to be a rant... To understand why this is going to require a rant, dear reader, please do the following: 1. Read the issue thread and follow the reproduction scenario (download map linked, fire up autoplay, seek near end, wait for results, hear the sample spam). 2. Now exit out to song select, *hide the toolbar*, and attempt reproducing the issue again. 3. Depending on ambient mood, laugh or cry. Now, *why on earth* would the *TOOLBAR* have any bearing on anything? Well, the chain of failure is something like this: - The toolbar hides for the duration of gameplay, naturally. - When progressing to results, the toolbar gets automatically unhidden. - This triggers invalidations on `ScrollingHitObjectContainer`. I'm not precisely sure which property it is that triggers the invalidations, but one clearly does. It may be position or size or whichever. - When the invalidation is triggered on `layoutCache`, the next `Update()` call is going to recompute lifetimes for ALL hitobject entries. - In case of swells, it happens that the calculated lifetime end of the swell is larger than what it actually ended up being determined as at the instant of judging the swell, and thus, the swell is *resurrected*, reassigned a DHO, and the DHO calls `UpdateState()` and plays the sample again despite the `samplePlayed` flag in `LegacySwell`, because all of that is ephemeral state that does not survive a hitobject getting resurrected. Now I *could* just fix this locally to the swell, maybe, by having some time lenience check, but the fact that hitobjects can be resurrected by the *toolbar* appearing, of all possible causes in the world, feels just completely wrong. So I'm adding a local check in SHOC to not overwrite lifetime ends of judged object entries. The reason why I'm making that check specific to end time is that I can see valid reasons why you would want to recompute lifetime *start* even on a judged object (such as playfield geometry changing in a significant way). I can't think of a valid reason to do that to lifetime *end*. --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 7841e65935..8b0076afa1 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -247,7 +247,12 @@ namespace osu.Game.Rulesets.UI.Scrolling // It is required that we set a lifetime end here to ensure that in scenarios like loading a Player instance to a seeked // location in a beatmap doesn't churn every hit object into a DrawableHitObject. Even in a pooled scenario, the overhead // of this can be quite crippling. - entry.LifetimeEnd = entry.HitObject.GetEndTime() + timeRange.Value; + // + // However, additionally do not attempt to alter lifetime of judged entries. + // This is to prevent freak accidents like objects suddenly becoming alive because of this estimate assigning a later lifetime + // than the object itself decided it should have when it underwent judgement. + if (!entry.Judged) + entry.LifetimeEnd = entry.HitObject.GetEndTime() + timeRange.Value; } private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null) From e8f7bcb6e625a6360b2bd4487186ac075e07ddc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 15:06:02 +0100 Subject: [PATCH 85/92] Only show team channel section when there is a team channel --- osu.Game.Tests/Visual/Online/TestSceneChannelList.cs | 6 +----- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 7 +++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index 8f8cf036f1..364240502a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -115,11 +115,7 @@ namespace osu.Game.Tests.Visual.Online channelList.AddChannel(createRandomPrivateChannel()); }); - AddStep("Add Team Channels", () => - { - for (int i = 0; i < 10; i++) - channelList.AddChannel(createRandomTeamChannel()); - }); + AddStep("Add Team Channel", () => channelList.AddChannel(createRandomTeamChannel())); AddStep("Add Announce Channels", () => { diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index ae68c9c82e..c0fc349c2c 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -106,6 +106,7 @@ namespace osu.Game.Overlays.Chat.ChannelList }; selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); + updateVisibility(); } public void AddChannel(Channel channel) @@ -170,10 +171,8 @@ namespace osu.Game.Overlays.Chat.ChannelList private void updateVisibility() { - if (AnnounceChannelGroup.ItemFlow.Children.Count == 0) - AnnounceChannelGroup.Hide(); - else - AnnounceChannelGroup.Show(); + AnnounceChannelGroup.Alpha = AnnounceChannelGroup.ItemFlow.Any() ? 1 : 0; + TeamChannelGroup.Alpha = TeamChannelGroup.ItemFlow.Any() ? 1 : 0; } public partial class ChannelGroup : FillFlowContainer From e13aa4a99b353f994e9bcf6c6df58d18f466bf66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 15:10:20 +0100 Subject: [PATCH 86/92] Do not allow leaving team channels --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 8 ++++++-- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index c0fc349c2c..0a89775cc7 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -114,9 +114,13 @@ namespace osu.Game.Overlays.Chat.ChannelList if (channelMap.ContainsKey(channel)) return; - ChannelListItem item = new ChannelListItem(channel); + ChannelListItem item = new ChannelListItem(channel) + { + CanLeave = channel.Type != ChannelType.Team + }; item.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); - item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan); + if (item.CanLeave) + item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan); ChannelGroup group = getGroupFromChannel(channel); channelMap.Add(channel, item); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index b197fe199d..6107f130ec 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Chat.ChannelList public partial class ChannelListItem : OsuClickableContainer, IFilterable { public event Action? OnRequestSelect; + + public bool CanLeave { get; init; } = true; public event Action? OnRequestLeave; public readonly Channel Channel; @@ -160,7 +162,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private ChannelListItemCloseButton? createCloseButton() { - if (isSelector) + if (isSelector || !CanLeave) return null; return new ChannelListItemCloseButton From c82cf4092879167f60bd76729730350d882c248f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 15:24:18 +0100 Subject: [PATCH 87/92] Do not give swell ticks any visual representation Why is this a thing at all? How has it survived this long? I don't know. As far as I can tell this only manifests on selected beatmaps with "slow swells" that spend the entire beatmap moving in the background. On other beatmaps the tick is faded out, probably due to the initial transform application that normally "works" but fails hard on these slow swells. Can be seen on https://osu.ppy.sh/beatmapsets/1432454#taiko/2948222. --- .../Objects/Drawables/DrawableSwellTick.cs | 7 +------ .../Objects/Drawables/DrawableTaikoHitObject.cs | 6 +++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 04dd01e066..88554ba257 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -4,9 +4,7 @@ #nullable disable using JetBrains.Annotations; -using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -25,8 +23,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - protected override void UpdateInitialTransforms() => this.FadeOut(); - public void TriggerResult(bool hit) { HitObject.StartTime = Time.Current; @@ -43,7 +39,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(KeyBindingPressEvent e) => false; - protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), - _ => new TickPiece()); + protected override SkinnableDrawable CreateMainPiece() => null; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 0cf9651965..520ac2ba80 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -154,9 +154,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (MainPiece != null) Content.Remove(MainPiece, true); - Content.Add(MainPiece = CreateMainPiece()); + MainPiece = CreateMainPiece(); + + if (MainPiece != null) + Content.Add(MainPiece); } + [CanBeNull] protected abstract SkinnableDrawable CreateMainPiece(); } } From 1f562ab47d5f6959f66e0091ec82b778c3539f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Feb 2025 09:18:19 +0100 Subject: [PATCH 88/92] Fix double-clicking difficulty adjust sliders not resetting the value to default correctly - Closes https://github.com/ppy/osu/issues/31888 - Supersedes / closes https://github.com/ppy/osu/pull/32060 --- .../Mods/OsuModDifficultyAdjust.cs | 9 +------- .../UserInterface/RoundedSliderBar.cs | 18 +++++++++++---- .../Mods/DifficultyAdjustSettingsControl.cs | 23 +++++++++++++------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index f35b1abc42..10282ff988 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -63,13 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods private partial class ApproachRateSettingsControl : DifficultyAdjustSettingsControl { - protected override RoundedSliderBar CreateSlider(BindableNumber current) => - new ApproachRateSlider - { - RelativeSizeAxes = Axes.X, - Current = current, - KeyboardStep = 0.1f, - }; + protected override RoundedSliderBar CreateSlider(BindableNumber current) => new ApproachRateSlider(); /// /// A slider bar with more detailed approach rate info for its given value diff --git a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs index aeab7c34b2..9a0183da64 100644 --- a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Overlays; using Vector2 = osuTK.Vector2; @@ -52,10 +53,21 @@ namespace osu.Game.Graphics.UserInterface } } + /// + /// The action to use to reset the value of to the default. + /// Triggered on double click. + /// + public Action ResetToDefault { get; internal set; } + public RoundedSliderBar() { Height = Nub.HEIGHT; RangePadding = Nub.DEFAULT_EXPANDED_SIZE / 2; + ResetToDefault = () => + { + if (!Current.Disabled) + Current.SetDefault(); + }; Children = new Drawable[] { new Container @@ -102,11 +114,7 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.TopCentre, RelativePositionAxes = Axes.X, Current = { Value = true }, - OnDoubleClicked = () => - { - if (!Current.Disabled) - Current.SetDefault(); - }, + OnDoubleClicked = () => ResetToDefault.Invoke(), }, }, hoverClickSounds = new HoverClickSounds() diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index d04d7636ec..6697a8d848 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -31,12 +31,7 @@ namespace osu.Game.Rulesets.Mods protected sealed override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent, CreateSlider); - protected virtual RoundedSliderBar CreateSlider(BindableNumber current) => new RoundedSliderBar - { - RelativeSizeAxes = Axes.X, - Current = current, - KeyboardStep = 0.1f, - }; + protected virtual RoundedSliderBar CreateSlider(BindableNumber current) => new RoundedSliderBar(); /// /// Guards against beatmap values displayed on slider bars being transferred to user override. @@ -111,7 +106,21 @@ namespace osu.Game.Rulesets.Mods { InternalChildren = new Drawable[] { - createSlider(currentNumber) + createSlider(currentNumber).With(slider => + { + slider.RelativeSizeAxes = Axes.X; + slider.Current = currentNumber; + slider.KeyboardStep = 0.1f; + // this looks redundant, but isn't because of the various games this component plays + // (`Current` is nullable and represents the underlying setting value, + // `currentNumber` is not nullable and represents what is getting displayed, + // therefore without this, double-clicking the slider would reset `currentNumber` to its bogus default of 0). + slider.ResetToDefault = () => + { + if (!Current.Disabled) + Current.SetDefault(); + }; + }) }; AutoSizeAxes = Axes.Y; From e97c2fee0d2a57d2d13c2e20a76370daa325cd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Feb 2025 12:57:38 +0100 Subject: [PATCH 89/92] Update framework --- 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 d49acd7b27..d4b49e492a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 5ca49e80f6..d10a3d649a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From abc12abdedfbb315996d5c16e5556cc9837d1e17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2025 16:48:18 +0900 Subject: [PATCH 90/92] Fix `PlayerTeamFlag` skinnable component not showing team details during replay For now, let's fetch on demand. Note that song select local leaderboard has the same issue. I feel we should be doing a lot more cached lookups (probaly with persisting across game restarts). Maybe even replacing the realm user storage. An issue for another day. --- osu.Game/Screens/Play/HUD/PlayerTeamFlag.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerTeamFlag.cs b/osu.Game/Screens/Play/HUD/PlayerTeamFlag.cs index f8ef03c58c..3f72099a45 100644 --- a/osu.Game/Screens/Play/HUD/PlayerTeamFlag.cs +++ b/osu.Game/Screens/Play/HUD/PlayerTeamFlag.cs @@ -3,8 +3,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Skinning; @@ -40,10 +42,19 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load() + private void load(UserLookupCache userLookupCache) { if (gameplayState != null) - flag.Team = gameplayState.Score.ScoreInfo.User.Team; + { + if (gameplayState.Score.ScoreInfo.User.Team != null) + flag.Team = gameplayState.Score.ScoreInfo.User.Team; + else + { + // We only store very basic information about a user to realm, so there's a high chance we don't have the team information. + userLookupCache.GetUserAsync(gameplayState.Score.ScoreInfo.User.Id) + .ContinueWith(task => Schedule(() => flag.Team = task.GetResultSafely()?.Team)); + } + } else { apiUser = api.LocalUser.GetBoundCopy(); From e8b7ec0f9537db864b712ebc28ba63afabe3eeb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2025 17:01:48 +0900 Subject: [PATCH 91/92] Adjust leaderboard score design slightly This design is about to get replaced, so I'm just making some minor adjustments since a lot of people complained about the font size in the last update. Of note, I'm only changing the font size which is one pt size lower than we'd usually use. Also overlapping the mod icons to create a bit more space (since there's already cases where they don't fit). Closes https://github.com/ppy/osu/issues/32055 as far as I'm concerned. I can read everything fine at 0.8x UI scale. --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index fc30f158f0..7306c2d21e 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -271,6 +271,7 @@ namespace osu.Game.Online.Leaderboards Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, AutoSizeAxes = Axes.Both, + Spacing = new Vector2(-10, 0), Direction = FillDirection.Horizontal, ChildrenEnumerable = Score.Mods.AsOrdered().Select(mod => new ModIcon(mod) { Scale = new Vector2(0.34f) }) }, @@ -425,7 +426,7 @@ namespace osu.Game.Online.Leaderboards public DateLabel(DateTimeOffset date) : base(date) { - Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold, italics: true); + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); } protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); From c45a403fe2b87db7b43d3500fe25e348b88e27ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Feb 2025 18:00:18 +0900 Subject: [PATCH 92/92] Mostly revert sizes --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 7306c2d21e..0db03efb68 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -395,7 +395,7 @@ namespace osu.Game.Online.Leaderboards Origin = Anchor.CentreLeft, Text = statistic.Value, Spacing = new Vector2(-1, 0), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, fixedWidth: true) + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold, fixedWidth: true) }, }, }; @@ -426,7 +426,7 @@ namespace osu.Game.Online.Leaderboards public DateLabel(DateTimeOffset date) : base(date) { - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold); } protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30));