1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-24 07:23:26 +08:00
osu-lazer/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2ArtistGrouping.cs
Dean Herbert 024fbde0fd
Refactor selection and activation handling
I had a bit of a struggle getting header traversal logic to work well.
The constraints I had in place were a bit weird:

- Group panels should toggle or potentially fall into the prev/next
  group
- Set panels should just traverse around them

The current method of using `CheckValidForGroupSelection` return type
for traversal did not mesh with the above two cases. Just trust me on
this one since it's quite hard to explain in words.

After some re-thinking, I've gone with a simpler approach with one
important change to UX: Now when group traversing with a beatmap set
header currently keyboard focused, the first operation will be to reset
keyboard selection to the selected beatmap, rather than traverse.

I find this non-offensive – at most it means a user will need to press
their group traversal key one extra time.

I've also changed group headers to always toggle expansion when doing
group traversal with them selected.

To make all this work, the meaning of `Activation` has changed somewhat.
It is now the primary path for carousel implementations to change
selection of an item. It is what the `Drawable` panels call when they
are clicked.

Selection changes are not performed implicitly by `Carousel` – an
implementation should decide when it actually wants to change the
selection, usually in `HandleItemActivated`.

Having less things mutating `CurrentSelection` is better in my eyes, as
we see this variable as only being mutated internally when utmost
required (ie the user has requested the change). With this change,
`CurrentSelection` can no longer become of a non-`T` type (in the
beatmap carousel implementation at least).

This might pave a path forward for making `CurrentSelection` typed, but
that comes with a few other concerns so I'll look at that as a
follow-up.
2025-02-06 17:02:44 +09:00

178 lines
6.8 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Testing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
using osu.Game.Screens.SelectV2;
namespace osu.Game.Tests.Visual.SongSelect
{
[TestFixture]
public partial class TestSceneBeatmapCarouselV2ArtistGrouping : BeatmapCarouselV2TestScene
{
[SetUpSteps]
public void SetUpSteps()
{
RemoveAllBeatmaps();
CreateCarousel();
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
AddBeatmaps(10, 3, true);
WaitForDrawablePanels();
}
[Test]
public void TestOpenCloseGroupWithNoSelectionMouse()
{
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
CheckNoSelection();
ClickVisiblePanel<GroupPanel>(0);
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
CheckNoSelection();
ClickVisiblePanel<GroupPanel>(0);
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
CheckNoSelection();
}
[Test]
public void TestOpenCloseGroupWithNoSelectionKeyboard()
{
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
CheckNoSelection();
SelectNextPanel();
Select();
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().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<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
CheckNoSelection();
}
[Test]
public void TestCarouselRemembersSelection()
{
SelectNextGroup();
object? selection = null;
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
CheckHasSelection();
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
RemoveAllBeatmaps();
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
AddBeatmaps(10);
WaitForDrawablePanels();
CheckHasSelection();
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
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);
ClickVisiblePanel<GroupPanel>(0);
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
ClickVisiblePanel<GroupPanel>(0);
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
}
[Test]
public void TestGroupSelectionOnHeader()
{
SelectNextGroup();
WaitForGroupSelection(0, 1);
SelectPrevPanel();
SelectPrevPanel();
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
SelectPrevGroup();
WaitForGroupSelection(0, 1);
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
SelectPrevGroup();
WaitForGroupSelection(0, 1);
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
}
[Test]
public void TestKeyboardSelection()
{
SelectNextPanel();
SelectNextPanel();
SelectNextPanel();
SelectNextPanel();
CheckNoSelection();
// open first group
Select();
CheckNoSelection();
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
SelectNextPanel();
Select();
WaitForGroupSelection(3, 1);
SelectNextGroup();
WaitForGroupSelection(3, 5);
SelectNextGroup();
WaitForGroupSelection(4, 1);
SelectPrevGroup();
WaitForGroupSelection(3, 5);
SelectNextGroup();
WaitForGroupSelection(4, 1);
SelectNextGroup();
WaitForGroupSelection(4, 5);
SelectNextGroup();
WaitForGroupSelection(0, 1);
SelectNextPanel();
SelectNextPanel();
SelectNextPanel();
SelectNextPanel();
SelectNextGroup();
WaitForGroupSelection(0, 1);
SelectNextPanel();
SelectNextGroup();
WaitForGroupSelection(1, 1);
}
}
}