From fc5832ce67d7af1b32f88109b705788c4bf07e07 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 04:44:06 -0500 Subject: [PATCH 1/6] Support variable spacing between carousel items --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 11 +++++++ osu.Game/Screens/SelectV2/Carousel.cs | 33 +++++++++++++------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 9f62780ddaff..12660d864221 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -20,12 +20,23 @@ namespace osu.Game.Screens.SelectV2 [Cached] public partial class BeatmapCarousel : Carousel { + public const float SPACING = 5f; + private IBindableList detachedBeatmaps = null!; private readonly LoadingLayer loading; private readonly BeatmapCarouselFilterGrouping grouping; + protected override float GetSpacingBetweenPanels(CarouselItem top, CarouselItem bottom) + { + if (top.Model is BeatmapInfo || bottom.Model is BeatmapInfo) + // Beatmap difficulty panels do not overlap with themselves or any other panel. + return SPACING; + + return -SPACING; + } + public BeatmapCarousel() { DebounceDelay = 100; diff --git a/osu.Game/Screens/SelectV2/Carousel.cs b/osu.Game/Screens/SelectV2/Carousel.cs index 608ef207d910..d7b6f251c33c 100644 --- a/osu.Game/Screens/SelectV2/Carousel.cs +++ b/osu.Game/Screens/SelectV2/Carousel.cs @@ -50,11 +50,6 @@ public abstract partial class Carousel : CompositeDrawable, IKeyBindingHandle /// public float DistanceOffscreenToPreload { get; set; } - /// - /// Vertical space between panel layout. Negative value can be used to create an overlapping effect. - /// - protected float SpacingBetweenPanels { get; set; } = -5; - /// /// When a new request arrives to change filtering, the number of milliseconds to wait before performing the filter. /// Regardless of any external debouncing, this is a safety measure to avoid triggering too many threaded operations. @@ -116,6 +111,11 @@ public void TryActivateSelection() } } + /// + /// Returns the vertical spacing between two given carousel items. Negative value can be used to create an overlapping effect. + /// + protected virtual float GetSpacingBetweenPanels(CarouselItem top, CarouselItem bottom) => 0f; + #endregion #region Properties and methods concerning implementations @@ -260,7 +260,7 @@ await Task.Run(async () => } log("Updating Y positions"); - updateYPositions(items, visibleHalfHeight, SpacingBetweenPanels); + updateYPositions(items, visibleHalfHeight); } catch (OperationCanceledException) { @@ -283,17 +283,26 @@ await Task.Run(async () => void log(string text) => Logger.Log($"Carousel[op {cts.GetHashCode().ToString()}] {stopwatch.ElapsedMilliseconds} ms: {text}"); } - private static void updateYPositions(IEnumerable carouselItems, float offset, float spacing) + private void updateYPositions(IEnumerable carouselItems, float offset) { + CarouselItem? previousVisible = null; + foreach (var item in carouselItems) - updateItemYPosition(item, ref offset, spacing); + updateItemYPosition(item, ref previousVisible, ref offset); } - private static void updateItemYPosition(CarouselItem item, ref float offset, float spacing) + private void updateItemYPosition(CarouselItem item, ref CarouselItem? previousVisible, ref float offset) { + float spacing = previousVisible == null || !item.IsVisible ? 0 : GetSpacingBetweenPanels(previousVisible, item); + + offset += spacing; item.CarouselYPosition = offset; + if (item.IsVisible) - offset += item.DrawHeight + spacing; + { + offset += item.DrawHeight; + previousVisible = item; + } } #endregion @@ -470,7 +479,7 @@ private void refreshAfterSelection() return; } - float spacing = SpacingBetweenPanels; + CarouselItem? lastVisible = null; int count = carouselItems.Count; Selection prevKeyboard = currentKeyboardSelection; @@ -482,7 +491,7 @@ private void refreshAfterSelection() { var item = carouselItems[i]; - updateItemYPosition(item, ref yPos, spacing); + updateItemYPosition(item, ref lastVisible, ref yPos); if (ReferenceEquals(item.Model, currentKeyboardSelection.Model)) currentKeyboardSelection = new Selection(item.Model, item, item.CarouselYPosition, i); From c389dbc711cc90aa2bd7c942d479f9c5336b377f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 04:45:32 -0500 Subject: [PATCH 2/6] Extend panel input area to cover gaps --- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 10 ++++++++++ osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 10 ++++++++++ osu.Game/Screens/SelectV2/GroupPanel.cs | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index 3edfd4203b15..2fe509402b5c 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -24,6 +24,16 @@ public partial class BeatmapPanel : PoolableDrawable, ICarouselPanel private Box activationFlash = null!; private OsuSpriteText text = null!; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = DrawRectangle; + + // Cover the gaps introduced by the spacing between BeatmapPanels. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + + return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index 79ffe0f68a17..85d5cc097d4d 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -27,6 +27,16 @@ public partial class BeatmapSetPanel : PoolableDrawable, ICarouselPanel private OsuSpriteText text = null!; private Box box = null!; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = DrawRectangle; + + // 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 }); + + return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index 7ed256ca6aa7..df930a311128 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -28,6 +28,16 @@ public partial class GroupPanel : PoolableDrawable, ICarouselPanel private Box box = 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() { From 6037d5d8ce256fe70d6a7b22a723d1e26fb6ea42 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 5 Feb 2025 04:46:05 -0500 Subject: [PATCH 3/6] Add test coverage --- ...estSceneBeatmapCarouselV2GroupSelection.cs | 62 ++++++++++++++++ .../TestSceneBeatmapCarouselV2Selection.cs | 70 ++++++++++++++----- 2 files changed, 115 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs index f4d97be5a5fd..ebdc54864e2d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.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.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -8,6 +9,8 @@ using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; using osu.Game.Screens.SelectV2; +using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { @@ -154,5 +157,64 @@ public void TestKeyboardSelection() SelectPrevGroup(); WaitForGroupSelection(2, 9); } + + [Test] + public void TestInputHandlingWithinGaps() + { + AddBeatmaps(5, 2); + WaitForDrawablePanels(); + SelectNextGroup(); + + clickOnPanel(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + WaitForGroupSelection(0, 1); + + clickOnPanel(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + WaitForGroupSelection(0, 0); + + SelectNextPanel(); + Select(); + WaitForGroupSelection(0, 1); + + clickOnGroup(0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 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)); + WaitForGroupSelection(0, 4); + + clickOnGroup(1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + AddAssert("group 1 expanded", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(1).Expanded.Value, () => Is.True); + } + + private void clickOnGroup(int group, Func pos) + { + AddStep($"click on group{group}", () => + { + var groupingFilter = Carousel.Filters.OfType().Single(); + var model = groupingFilter.GroupItems.Keys.ElementAt(group); + + var panel = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); + InputManager.MoveMouseTo(panel.ToScreenSpace(pos(panel))); + InputManager.Click(MouseButton.Left); + }); + } + + private void clickOnPanel(int group, int panel, Func pos) + { + AddStep($"click on group{group} panel{panel}", () => + { + var groupingFilter = Carousel.Filters.OfType().Single(); + + var g = groupingFilter.GroupItems.Keys.ElementAt(group); + // offset by one because the group itself is included in the items list. + object model = groupingFilter.GroupItems[g].ElementAt(panel + 1).Model; + + var p = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); + InputManager.MoveMouseTo(p.ToScreenSpace(pos(p))); + InputManager.Click(MouseButton.Left); + }); + } } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs index b087c252e418..5541e217cfdf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2Selection.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Screens.SelectV2; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect @@ -94,9 +96,8 @@ public void TestCarouselRemembersSelection() AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!)); - AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection)); AddUntilStep("drawable selection restored", () => getSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection)); - AddAssert("carousel item is visible", () => getSelectedPanel()?.Item?.IsVisible, () => Is.True); + AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection)); BeatmapPanel? getSelectedPanel() => Carousel.ChildrenOfType().SingleOrDefault(p => p.Selected.Value); } @@ -129,21 +130,6 @@ public void TestTraversalBeyondEnd() WaitForSelection(0, 0); } - [Test] - public void TestGroupSelectionOnHeader() - { - AddBeatmaps(10, 3); - WaitForDrawablePanels(); - - SelectNextGroup(); - SelectNextGroup(); - WaitForSelection(1, 0); - - SelectPrevPanel(); - SelectPrevGroup(); - WaitForSelection(0, 0); - } - [Test] public void TestKeyboardSelection() { @@ -194,6 +180,34 @@ public void TestEmptyTraversal() CheckNoSelection(); } + [Test] + public void TestInputHandlingWithinGaps() + { + AddBeatmaps(2, 5); + WaitForDrawablePanels(); + SelectNextGroup(); + + clickOnDifficulty(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + WaitForSelection(0, 1); + + clickOnDifficulty(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + WaitForSelection(0, 0); + + SelectNextPanel(); + Select(); + WaitForSelection(0, 1); + + clickOnSet(0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + WaitForSelection(0, 0); + + AddStep("scroll to end", () => Scroll.ScrollToEnd(false)); + clickOnDifficulty(0, 4, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + WaitForSelection(0, 4); + + clickOnSet(1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); + WaitForSelection(1, 0); + } + private void checkSelectionIterating(bool isIterating) { object? selection = null; @@ -207,5 +221,27 @@ private void checkSelectionIterating(bool isIterating) AddUntilStep("selection not changed", () => Carousel.CurrentSelection == selection); } } + + private void clickOnSet(int set, Func pos) + { + AddStep($"click on set{set}", () => + { + var model = BeatmapSets[set]; + var panel = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); + InputManager.MoveMouseTo(panel.ToScreenSpace(pos(panel))); + InputManager.Click(MouseButton.Left); + }); + } + + private void clickOnDifficulty(int set, int diff, Func pos) + { + AddStep($"click on set{set} diff{diff}", () => + { + var model = BeatmapSets[set].Beatmaps[diff]; + var panel = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); + InputManager.MoveMouseTo(panel.ToScreenSpace(pos(panel))); + InputManager.Click(MouseButton.Left); + }); + } } } From d73f275143a7c36a8b629f15ea061678cba5ba1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Feb 2025 15:15:58 +0900 Subject: [PATCH 4/6] Don't inflate set / group panels for simplicity --- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 7 +++++-- osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 10 ---------- osu.Game/Screens/SelectV2/GroupPanel.cs | 10 ---------- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index b690e35a485a..ddf2fdcb57d3 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -28,8 +28,11 @@ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { var inputRectangle = DrawRectangle; - // Cover the gaps introduced by the spacing between BeatmapPanels. - inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f }); + // Cover the gaps introduced by the spacing between BeatmapPanels so that clicks will not fall through the carousel. + // + // 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 }); return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); } diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index d869e0af7527..f6c9324077b2 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -26,16 +26,6 @@ public partial class BeatmapSetPanel : PoolableDrawable, ICarouselPanel private OsuSpriteText text = null!; private Box box = null!; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) - { - var inputRectangle = DrawRectangle; - - // 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 }); - - return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); - } - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index c5e5c7745f33..e10521f63ea6 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -27,16 +27,6 @@ public partial class GroupPanel : PoolableDrawable, ICarouselPanel private Box box = 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() { From d505c529cd217abfbf697a5e9f9f8c1ebb2da14c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Feb 2025 15:06:21 +0900 Subject: [PATCH 5/6] Adjust tests in line with new expectations --- .../SongSelect/BeatmapCarouselV2TestScene.cs | 28 +++++++++ ...ceneBeatmapCarouselV2DifficultyGrouping.cs | 62 ++++--------------- .../TestSceneBeatmapCarouselV2NoGrouping.cs | 54 +++++----------- 3 files changed, 57 insertions(+), 87 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs index f17f312e9f9d..be0d0bf79a4a 100644 --- a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs +++ b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -20,6 +21,7 @@ using osu.Game.Screens.SelectV2; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; +using osuTK; using osuTK.Graphics; using osuTK.Input; using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel; @@ -164,6 +166,15 @@ protected void WaitForSelection(int set, int? diff = null) }); } + protected IEnumerable GetVisiblePanels() + where T : Drawable + { + return Carousel.ChildrenOfType().Single() + .ChildrenOfType() + .Where(p => ((ICarouselPanel)p).Item?.IsVisible == true) + .OrderBy(p => p.Y); + } + protected void ClickVisiblePanel(int index) where T : Drawable { @@ -178,6 +189,23 @@ protected void ClickVisiblePanel(int index) }); } + protected void ClickVisiblePanelWithOffset(int index, Vector2 positionOffsetFromCentre) + where T : Drawable + { + AddStep($"move mouse to panel {index} with offset {positionOffsetFromCentre}", () => + { + var panel = Carousel.ChildrenOfType().Single() + .ChildrenOfType() + .Where(p => ((ICarouselPanel)p).Item?.IsVisible == true) + .OrderBy(p => p.Y) + .ElementAt(index); + + InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre + panel.ToScreenSpace(positionOffsetFromCentre) - panel.ToScreenSpace(Vector2.Zero)); + }); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + } + /// /// Add requested beatmap sets count to list. /// diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs index 83e0e77fa670..f631dfc562ad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2DifficultyGrouping.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -10,7 +9,6 @@ using osu.Game.Screens.Select.Filter; using osu.Game.Screens.SelectV2; using osuTK; -using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { @@ -153,60 +151,24 @@ public void TestKeyboardSelection() [Test] public void TestInputHandlingWithinGaps() { - AddBeatmaps(5, 2); - WaitForDrawablePanels(); - SelectNextGroup(); - - clickOnPanel(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); - WaitForGroupSelection(0, 1); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); - clickOnPanel(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); - WaitForGroupSelection(0, 0); - - SelectNextPanel(); - Select(); - WaitForGroupSelection(0, 1); + // Clicks just above the first group panel should not actuate any action. + ClickVisiblePanelWithOffset(0, new Vector2(0, -(GroupPanel.HEIGHT / 2 + 1))); - clickOnGroup(0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 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); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); - AddStep("scroll to end", () => Scroll.ScrollToEnd(false)); - clickOnPanel(0, 4, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); - WaitForGroupSelection(0, 4); + ClickVisiblePanelWithOffset(0, new Vector2(0, -(GroupPanel.HEIGHT / 2))); - clickOnGroup(1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); - AddAssert("group 1 expanded", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(1).Expanded.Value, () => Is.True); - } + AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels().Any()); + CheckNoSelection(); - private void clickOnGroup(int group, Func pos) - { - AddStep($"click on group{group}", () => - { - var groupingFilter = Carousel.Filters.OfType().Single(); - var model = groupingFilter.GroupItems.Keys.ElementAt(group); - - var panel = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); - InputManager.MoveMouseTo(panel.ToScreenSpace(pos(panel))); - InputManager.Click(MouseButton.Left); - }); - } + // Beatmap panels expand their selection area to cover holes from spacing. + ClickVisiblePanelWithOffset(0, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + WaitForGroupSelection(0, 0); - private void clickOnPanel(int group, int panel, Func pos) - { - AddStep($"click on group{group} panel{panel}", () => - { - var groupingFilter = Carousel.Filters.OfType().Single(); - - var g = groupingFilter.GroupItems.Keys.ElementAt(group); - // offset by one because the group itself is included in the items list. - object model = groupingFilter.GroupItems[g].ElementAt(panel + 1).Model; - - var p = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); - InputManager.MoveMouseTo(p.ToScreenSpace(pos(p))); - InputManager.Click(MouseButton.Left); - }); + 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 566c2f1798f2..1359b5c58e8d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -209,31 +208,34 @@ public void TestEmptyTraversal() } [Test] + [Solo] public void TestInputHandlingWithinGaps() { AddBeatmaps(2, 5); WaitForDrawablePanels(); - SelectNextGroup(); - clickOnDifficulty(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); - WaitForSelection(0, 1); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); - clickOnDifficulty(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); - WaitForSelection(0, 0); + // Clicks just above the first group panel should not actuate any action. + ClickVisiblePanelWithOffset(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2 + 1))); - SelectNextPanel(); - Select(); - WaitForSelection(0, 1); + AddAssert("no beatmaps visible", () => !GetVisiblePanels().Any()); - clickOnSet(0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); + ClickVisiblePanelWithOffset(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2))); + + 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))); WaitForSelection(0, 0); - AddStep("scroll to end", () => Scroll.ScrollToEnd(false)); - clickOnDifficulty(0, 4, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); - WaitForSelection(0, 4); + // Panels with higher depth will handle clicks in the gutters for simplicity. + ClickVisiblePanelWithOffset(2, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + WaitForSelection(0, 2); - clickOnSet(1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); - WaitForSelection(1, 0); + ClickVisiblePanelWithOffset(3, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1))); + WaitForSelection(0, 3); } private void checkSelectionIterating(bool isIterating) @@ -249,27 +251,5 @@ private void checkSelectionIterating(bool isIterating) AddUntilStep("selection not changed", () => Carousel.CurrentSelection == selection); } } - - private void clickOnSet(int set, Func pos) - { - AddStep($"click on set{set}", () => - { - var model = BeatmapSets[set]; - var panel = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); - InputManager.MoveMouseTo(panel.ToScreenSpace(pos(panel))); - InputManager.Click(MouseButton.Left); - }); - } - - private void clickOnDifficulty(int set, int diff, Func pos) - { - AddStep($"click on set{set} diff{diff}", () => - { - var model = BeatmapSets[set].Beatmaps[diff]; - var panel = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); - InputManager.MoveMouseTo(panel.ToScreenSpace(pos(panel))); - InputManager.Click(MouseButton.Left); - }); - } } } From aa329f397e684f41e5ca040d4d33a13026d464a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Feb 2025 15:30:31 +0900 Subject: [PATCH 6/6] Remove stray `[Solo]`s --- osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs | 1 - .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 1 - .../Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs | 1 - osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 1 - 4 files changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index 966e6513bb54..4953cf83c9d5 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -74,7 +74,6 @@ public void TestContextMenu() } [Test] - [Solo] public void TestCommitPlacementViaRightClick() { Playfield playfield = null!; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index d76e0290efb5..ee5b1797ed30 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -165,7 +165,6 @@ private void makeMetadataChange(bool commit = true) } [Test] - [Solo] public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() { prepareBeatmap(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs index 1359b5c58e8d..09ded342c309 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2NoGrouping.cs @@ -208,7 +208,6 @@ public void TestEmptyTraversal() } [Test] - [Solo] public void TestInputHandlingWithinGaps() { AddBeatmaps(2, 5); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index c415fc876fe7..d8ab367ebd9d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1239,7 +1239,6 @@ public void TestTextBoxBeatmapDifficultyCount() } [Test] - [Solo] public void TestHardDeleteHandledCorrectly() { createSongSelect();