From 0302a3719031466e776a556b012ccf311dcaaaed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 17:14:11 +0900 Subject: [PATCH 1/7] Remove misplaced assert --- osu.Game/Graphics/Carousel/Carousel.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index a9655aab56..d5b47dc9a6 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -801,12 +801,7 @@ namespace osu.Game.Graphics.Carousel base.OffsetScrollPosition(offset); foreach (var panel in Panels) - { - var c = (ICarouselPanel)panel; - Debug.Assert(c.Item != null); - - c.DrawYPosition += offset; - } + ((ICarouselPanel)panel).DrawYPosition += offset; } public override void Clear(bool disposeChildren) From 7efa724a8ac69ec86fde10a32a451334299354d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 17:14:27 +0900 Subject: [PATCH 2/7] Fix depth flip-flopping during initial animation --- osu.Game/Graphics/Carousel/Carousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index d5b47dc9a6..cdcb933526 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -623,7 +623,7 @@ namespace osu.Game.Graphics.Carousel if (c.Item == null) continue; - float normalisedDepth = (float)(Math.Abs(selectedYPos - c.DrawYPosition) / DrawHeight); + float normalisedDepth = (float)(Math.Abs(selectedYPos - c.Item.CarouselYPosition) / DrawHeight); Scroll.Panels.ChangeChildDepth(panel, c.Item.DepthLayer + normalisedDepth); if (c.DrawYPosition != c.Item.CarouselYPosition) From 6a2337dbea348dff90c3953fd388a89462444f33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 17:21:49 +0900 Subject: [PATCH 3/7] Add fade animation when carousel panels are expired This fixes the hard cut that was previously very jarring. --- osu.Game/Graphics/Carousel/Carousel.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index cdcb933526..ebe1d7d77a 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -710,7 +710,7 @@ namespace osu.Game.Graphics.Carousel } // If the new display range doesn't contain the panel, it's no longer required for display. - expirePanelImmediately(panel); + expirePanel(panel); } // Add any new items which need to be displayed and haven't yet. @@ -738,13 +738,18 @@ namespace osu.Game.Graphics.Carousel Scroll.SetLayoutHeight(0); } - private static void expirePanelImmediately(Drawable panel) + private void expirePanel(Drawable panel) { - panel.FinishTransforms(); - panel.Expire(); - var carouselPanel = (ICarouselPanel)panel; + // expired panels should have a depth behind all other panels to make the transition not look weird. + Scroll.Panels.ChangeChildDepth(panel, panel.Depth + 1); + + panel.FadeOut(150, Easing.OutQuint); + panel.MoveToX(panel.X + 100, 200, Easing.Out); + + panel.Expire(); + carouselPanel.Item = null; carouselPanel.Selected.Value = false; carouselPanel.KeyboardSelected.Value = false; From 615d4accfb89b54ecf97042682c12be71b948f0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 17:23:08 +0900 Subject: [PATCH 4/7] Add new panels to carousel with a better initial Y position Previously, carousel panels would appear immediately at their required Y position. This didn't look great when they appear with other panels surrounding them. Now they will spawn in underneath the nearest item before them, making the animation feel much more correct. --- osu.Game/Graphics/Carousel/Carousel.cs | 32 ++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index ebe1d7d77a..0102434bbd 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -695,6 +695,12 @@ namespace osu.Game.Graphics.Carousel { var carouselPanel = (ICarouselPanel)panel; + if (carouselPanel.Item == null) + { + // Item is null when a panel is already fading away from existence; should be ignored for tracking purposes. + continue; + } + // The case where we're intending to display this panel, but it's already displayed. // Note that we **must compare the model here** as the CarouselItems may be fresh instances due to a filter operation. // @@ -721,12 +727,34 @@ namespace osu.Game.Graphics.Carousel if (drawable is not ICarouselPanel carouselPanel) throw new InvalidOperationException($"Carousel panel drawables must implement {typeof(ICarouselPanel)}"); - carouselPanel.DrawYPosition = item.CarouselYPosition; carouselPanel.Item = item; - Scroll.Add(drawable); } + if (toDisplay.Any()) + { + // To make transitions of items appearing in the flow look good, do a pass and make sure newly added items spawn from + // just beneath the *current interpolated position* of the previous panel. + var orderedPanels = Scroll.Panels + .OfType() + .Where(p => p.Item != null) + .OrderBy(p => p.Item!.CarouselYPosition) + .ToList(); + + for (int i = 0; i < orderedPanels.Count; i++) + { + var panel = orderedPanels[i]; + + if (toDisplay.Contains(panel.Item!)) + { + if (i == 0) + panel.DrawYPosition = panel.Item!.CarouselYPosition; + else + panel.DrawYPosition = orderedPanels[i - 1].DrawYPosition; + } + } + } + // Update the total height of all items (to make the scroll container scrollable through the full height even though // most items are not displayed / loaded). if (carouselItems.Count > 0) From a32da2ea1060b64ad523fc8f697e62dbfd4ab058 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 17:45:32 +0900 Subject: [PATCH 5/7] Avoid animating list tail At very high scroll speeds (using pageup/pagedown) you would see a weird animation of panels appearing. This removes the animation for that edge case. --- osu.Game/Graphics/Carousel/Carousel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 0102434bbd..09f8962632 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -747,10 +747,12 @@ namespace osu.Game.Graphics.Carousel if (toDisplay.Contains(panel.Item!)) { - if (i == 0) - panel.DrawYPosition = panel.Item!.CarouselYPosition; - else + // Don't apply to the last because animating the tail of the list looks bad. + // It's usually off-screen anyway. + if (i > 0 && i < orderedPanels.Count - 1) panel.DrawYPosition = orderedPanels[i - 1].DrawYPosition; + else + panel.DrawYPosition = panel.Item!.CarouselYPosition; } } } From a8b247f7f13e4990e6be7fc7483b9fba41e61c98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 15:31:08 +0900 Subject: [PATCH 6/7] SongSelectV2: Add support for deselecting all mods by right clicking mod button Addresses https://github.com/ppy/osu/discussions/28301. No sound effects on click because right click handling is weird. I would have liked to call `DeselectAll` on the mod select overlay, but this doesn't work unless it's displayed due to queued operation. --- .../Visual/SongSelectV2/TestSceneSongSelect.cs | 15 +++++++++++++++ osu.Game/Screens/SelectV2/FooterButtonMods.cs | 16 ++++++++++++++++ osu.Game/Screens/SelectV2/SongSelect.cs | 8 +++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index 1bf9fecbb8..76a1683985 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -52,6 +52,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddAssert("beatmap set deleted", () => Beatmaps.GetAllUsableBeatmapSets().Any(), () => Is.False); } + [Test] + public void TestClearModsViaModButtonRightClick() + { + LoadSongSelect(); + + AddStep("select NC", () => SelectedMods.Value = new[] { new OsuModNightcore() }); + AddAssert("mods selected", () => SelectedMods.Value, () => Has.Count.EqualTo(1)); + AddStep("right click mod button", () => + { + InputManager.MoveMouseTo(Footer.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Right); + }); + AddAssert("not mods selected", () => SelectedMods.Value, () => Has.Count.EqualTo(0)); + } + [Test] public void TestSpeedChange() { diff --git a/osu.Game/Screens/SelectV2/FooterButtonMods.cs b/osu.Game/Screens/SelectV2/FooterButtonMods.cs index 41ac1a2ff6..9e2b53012a 100644 --- a/osu.Game/Screens/SelectV2/FooterButtonMods.cs +++ b/osu.Game/Screens/SelectV2/FooterButtonMods.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics; @@ -27,11 +28,14 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Utils; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.SelectV2 { public partial class FooterButtonMods : ScreenFooterButton, IHasCurrentValue> { + public Action? RequestDeselectAllMods { get; init; } + private const float bar_height = 30f; private const float mod_display_portion = 0.65f; @@ -172,6 +176,18 @@ namespace osu.Game.Screens.SelectV2 FinishTransforms(true); } + protected override bool OnMouseDown(MouseDownEvent e) + { + // should probably be OnClick but right mouse button clicks isn't setup well. + if (e.Button == MouseButton.Right) + { + RequestDeselectAllMods?.Invoke(); + return true; + } + + return base.OnMouseDown(e); + } + private const double duration = 240; private const Easing easing = Easing.OutQuint; diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 43fa394e39..61d16c8bf5 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -21,6 +22,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Volume; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; @@ -194,7 +196,11 @@ namespace osu.Game.Screens.SelectV2 public override IReadOnlyList CreateFooterButtons() => new ScreenFooterButton[] { - new FooterButtonMods(modSelectOverlay) { Current = Mods }, + new FooterButtonMods(modSelectOverlay) + { + Current = Mods, + RequestDeselectAllMods = () => Mods.Value = Array.Empty() + }, new FooterButtonRandom(), new FooterButtonOptions(), }; From bfc23c98f1c7cf2135209ef6efb90fdc4f44bb07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 19:08:54 +0900 Subject: [PATCH 7/7] Use larger offset to ensure depth is still backmost even with `DepthLayer` is in use --- osu.Game/Graphics/Carousel/Carousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 09f8962632..37d69dab89 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -773,7 +773,7 @@ namespace osu.Game.Graphics.Carousel var carouselPanel = (ICarouselPanel)panel; // expired panels should have a depth behind all other panels to make the transition not look weird. - Scroll.Panels.ChangeChildDepth(panel, panel.Depth + 1); + Scroll.Panels.ChangeChildDepth(panel, panel.Depth + 1024); panel.FadeOut(150, Easing.OutQuint); panel.MoveToX(panel.X + 100, 200, Easing.Out);