mirror of
https://github.com/ppy/osu.git
synced 2025-02-21 15:43:36 +08:00
Merge branch 'master' into carousel-v2-spacing
This commit is contained in:
commit
1cf375e329
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -16,7 +17,6 @@ using osu.Game.Database;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Filter;
|
|
||||||
using osu.Game.Screens.SelectV2;
|
using osu.Game.Screens.SelectV2;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
@ -53,16 +53,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Scheduler.AddDelayed(updateStats, 100, true);
|
Scheduler.AddDelayed(updateStats, 100, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
|
||||||
public virtual void SetUpSteps()
|
|
||||||
{
|
|
||||||
RemoveAllBeatmaps();
|
|
||||||
|
|
||||||
CreateCarousel();
|
|
||||||
|
|
||||||
SortBy(new FilterCriteria { Sort = SortMode.Title });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void CreateCarousel()
|
protected void CreateCarousel()
|
||||||
{
|
{
|
||||||
AddStep("create components", () =>
|
AddStep("create components", () =>
|
||||||
@ -146,6 +136,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
protected void CheckNoSelection() => AddAssert("has no selection", () => Carousel.CurrentSelection, () => Is.Null);
|
protected void CheckNoSelection() => AddAssert("has no selection", () => Carousel.CurrentSelection, () => Is.Null);
|
||||||
protected void CheckHasSelection() => AddAssert("has selection", () => Carousel.CurrentSelection, () => Is.Not.Null);
|
protected void CheckHasSelection() => AddAssert("has selection", () => Carousel.CurrentSelection, () => Is.Not.Null);
|
||||||
|
|
||||||
|
protected BeatmapPanel? GetSelectedPanel() => Carousel.ChildrenOfType<BeatmapPanel>().SingleOrDefault(p => p.Selected.Value);
|
||||||
|
protected GroupPanel? GetKeyboardSelectedPanel() => Carousel.ChildrenOfType<GroupPanel>().SingleOrDefault(p => p.KeyboardSelected.Value);
|
||||||
|
|
||||||
protected void WaitForGroupSelection(int group, int panel)
|
protected void WaitForGroupSelection(int group, int panel)
|
||||||
{
|
{
|
||||||
AddUntilStep($"selected is group{group} panel{panel}", () =>
|
AddUntilStep($"selected is group{group} panel{panel}", () =>
|
||||||
@ -190,12 +183,41 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="count">The count of beatmap sets to add.</param>
|
/// <param name="count">The count of beatmap sets to add.</param>
|
||||||
/// <param name="fixedDifficultiesPerSet">If not null, the number of difficulties per set. If null, randomised difficulty count will be used.</param>
|
/// <param name="fixedDifficultiesPerSet">If not null, the number of difficulties per set. If null, randomised difficulty count will be used.</param>
|
||||||
protected void AddBeatmaps(int count, int? fixedDifficultiesPerSet = null) => AddStep($"add {count} beatmaps", () =>
|
/// <param name="randomMetadata">Whether to randomise the metadata to make groupings more uniform.</param>
|
||||||
|
protected void AddBeatmaps(int count, int? fixedDifficultiesPerSet = null, bool randomMetadata = false) => AddStep($"add {count} beatmaps{(randomMetadata ? " with random data" : "")}", () =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
BeatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(fixedDifficultiesPerSet ?? RNG.Next(1, 4)));
|
{
|
||||||
|
var beatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(fixedDifficultiesPerSet ?? RNG.Next(1, 4));
|
||||||
|
|
||||||
|
if (randomMetadata)
|
||||||
|
{
|
||||||
|
char randomCharacter = getRandomCharacter();
|
||||||
|
|
||||||
|
var metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
|
Artist = $"{randomCharacter}ome Artist " + RNG.Next(0, 9),
|
||||||
|
Title = $"{randomCharacter}ome Song (set id {beatmapSetInfo.OnlineID:000}) {Guid.NewGuid()}",
|
||||||
|
Author = { Username = $"{randomCharacter}ome Guy " + RNG.Next(0, 9) },
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var beatmap in beatmapSetInfo.Beatmaps)
|
||||||
|
beatmap.Metadata = metadata.DeepClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
BeatmapSets.Add(beatmapSetInfo);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private static long randomCharPointer;
|
||||||
|
|
||||||
|
private static char getRandomCharacter()
|
||||||
|
{
|
||||||
|
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*";
|
||||||
|
return chars[(int)((randomCharPointer++ / 2) % chars.Length)];
|
||||||
|
}
|
||||||
|
|
||||||
protected void RemoveAllBeatmaps() => AddStep("clear all beatmaps", () => BeatmapSets.Clear());
|
protected void RemoveAllBeatmaps() => AddStep("clear all beatmaps", () => BeatmapSets.Clear());
|
||||||
|
|
||||||
protected void RemoveFirstBeatmap() =>
|
protected void RemoveFirstBeatmap() =>
|
||||||
|
@ -2,100 +2,65 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics.Primitives;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
using osu.Game.Screens.SelectV2;
|
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Currently covers adding and removing of items and scrolling.
|
/// Covers common steps which can be used for manual testing.
|
||||||
/// If we add more tests here, these two categories can likely be split out into separate scenes.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneBeatmapCarouselV2Basics : BeatmapCarouselV2TestScene
|
public partial class TestSceneBeatmapCarouselV2 : BeatmapCarouselV2TestScene
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
|
[Explicit]
|
||||||
public void TestBasics()
|
public void TestBasics()
|
||||||
{
|
{
|
||||||
AddBeatmaps(1);
|
CreateCarousel();
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
|
||||||
|
AddBeatmaps(10, randomMetadata: true);
|
||||||
AddBeatmaps(10);
|
AddBeatmaps(10);
|
||||||
|
AddBeatmaps(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Explicit]
|
||||||
|
public void TestSorting()
|
||||||
|
{
|
||||||
|
SortBy(new FilterCriteria { Sort = SortMode.Artist });
|
||||||
|
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||||
|
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Explicit]
|
||||||
|
public void TestRemovals()
|
||||||
|
{
|
||||||
RemoveFirstBeatmap();
|
RemoveFirstBeatmap();
|
||||||
RemoveAllBeatmaps();
|
RemoveAllBeatmaps();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOffScreenLoading()
|
[Explicit]
|
||||||
{
|
public void TestAddRemoveRepeatedOps()
|
||||||
AddStep("disable masking", () => Scroll.Masking = false);
|
|
||||||
AddStep("enable masking", () => Scroll.Masking = true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestAddRemoveOneByOne()
|
|
||||||
{
|
{
|
||||||
AddRepeatStep("add beatmaps", () => BeatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4))), 20);
|
AddRepeatStep("add beatmaps", () => BeatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4))), 20);
|
||||||
AddRepeatStep("remove beatmaps", () => BeatmapSets.RemoveAt(RNG.Next(0, BeatmapSets.Count)), 20);
|
AddRepeatStep("remove beatmaps", () => BeatmapSets.RemoveAt(RNG.Next(0, BeatmapSets.Count)), 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSorting()
|
[Explicit]
|
||||||
|
public void TestMasking()
|
||||||
{
|
{
|
||||||
AddBeatmaps(10);
|
AddStep("disable masking", () => Scroll.Masking = false);
|
||||||
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
AddStep("enable masking", () => Scroll.Masking = true);
|
||||||
SortBy(new FilterCriteria { Sort = SortMode.Artist });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestScrollPositionMaintainedOnAddSecondSelected()
|
|
||||||
{
|
|
||||||
Quad positionBefore = default;
|
|
||||||
|
|
||||||
AddBeatmaps(10);
|
|
||||||
WaitForDrawablePanels();
|
|
||||||
|
|
||||||
AddStep("select middle beatmap", () => Carousel.CurrentSelection = BeatmapSets.ElementAt(BeatmapSets.Count - 2));
|
|
||||||
AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value)));
|
|
||||||
|
|
||||||
WaitForScrolling();
|
|
||||||
|
|
||||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
|
||||||
|
|
||||||
RemoveFirstBeatmap();
|
|
||||||
WaitForSorting();
|
|
||||||
|
|
||||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
|
||||||
() => Is.EqualTo(positionBefore));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestScrollPositionMaintainedOnAddLastSelected()
|
|
||||||
{
|
|
||||||
Quad positionBefore = default;
|
|
||||||
|
|
||||||
AddBeatmaps(10);
|
|
||||||
WaitForDrawablePanels();
|
|
||||||
|
|
||||||
AddStep("scroll to last item", () => Scroll.ScrollToEnd(false));
|
|
||||||
|
|
||||||
AddStep("select last beatmap", () => Carousel.CurrentSelection = BeatmapSets.Last());
|
|
||||||
|
|
||||||
WaitForScrolling();
|
|
||||||
|
|
||||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
|
||||||
|
|
||||||
RemoveFirstBeatmap();
|
|
||||||
WaitForSorting();
|
|
||||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
|
||||||
() => Is.EqualTo(positionBefore));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
@ -0,0 +1,177 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,23 +15,22 @@ using osuTK.Input;
|
|||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneBeatmapCarouselV2GroupSelection : BeatmapCarouselV2TestScene
|
public partial class TestSceneBeatmapCarouselV2DifficultyGrouping : BeatmapCarouselV2TestScene
|
||||||
{
|
{
|
||||||
public override void SetUpSteps()
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
RemoveAllBeatmaps();
|
RemoveAllBeatmaps();
|
||||||
|
|
||||||
CreateCarousel();
|
CreateCarousel();
|
||||||
|
|
||||||
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||||
|
|
||||||
|
AddBeatmaps(10, 3);
|
||||||
|
WaitForDrawablePanels();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOpenCloseGroupWithNoSelectionMouse()
|
public void TestOpenCloseGroupWithNoSelectionMouse()
|
||||||
{
|
{
|
||||||
AddBeatmaps(10, 5);
|
|
||||||
WaitForDrawablePanels();
|
|
||||||
|
|
||||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
CheckNoSelection();
|
CheckNoSelection();
|
||||||
|
|
||||||
@ -47,86 +46,79 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
||||||
{
|
{
|
||||||
AddBeatmaps(10, 5);
|
|
||||||
WaitForDrawablePanels();
|
|
||||||
|
|
||||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
CheckNoSelection();
|
CheckNoSelection();
|
||||||
|
|
||||||
SelectNextPanel();
|
SelectNextPanel();
|
||||||
Select();
|
Select();
|
||||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||||
AddAssert("keyboard selected is expanded", () => getKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
CheckNoSelection();
|
CheckNoSelection();
|
||||||
|
|
||||||
Select();
|
Select();
|
||||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().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);
|
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||||
CheckNoSelection();
|
CheckNoSelection();
|
||||||
|
|
||||||
GroupPanel? getKeyboardSelectedPanel() => Carousel.ChildrenOfType<GroupPanel>().SingleOrDefault(p => p.KeyboardSelected.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCarouselRemembersSelection()
|
public void TestCarouselRemembersSelection()
|
||||||
{
|
{
|
||||||
AddBeatmaps(10);
|
|
||||||
WaitForDrawablePanels();
|
|
||||||
|
|
||||||
SelectNextGroup();
|
SelectNextGroup();
|
||||||
|
|
||||||
object? selection = null;
|
object? selection = null;
|
||||||
|
|
||||||
AddStep("store drawable selection", () => selection = getSelectedPanel()?.Item?.Model);
|
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||||
|
|
||||||
CheckHasSelection();
|
CheckHasSelection();
|
||||||
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||||
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
|
||||||
RemoveAllBeatmaps();
|
RemoveAllBeatmaps();
|
||||||
AddUntilStep("no drawable selection", getSelectedPanel, () => Is.Null);
|
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
AddBeatmaps(10);
|
AddBeatmaps(10);
|
||||||
WaitForDrawablePanels();
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
CheckHasSelection();
|
CheckHasSelection();
|
||||||
AddAssert("no drawable selection", getSelectedPanel, () => Is.Null);
|
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||||
|
|
||||||
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
AddUntilStep("drawable selection restored", () => getSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||||
AddAssert("carousel item is visible", () => getSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||||
|
|
||||||
ClickVisiblePanel<GroupPanel>(0);
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
AddUntilStep("carousel item not visible", getSelectedPanel, () => Is.Null);
|
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
ClickVisiblePanel<GroupPanel>(0);
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
AddUntilStep("carousel item is visible", () => getSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||||
|
|
||||||
BeatmapPanel? getSelectedPanel() => Carousel.ChildrenOfType<BeatmapPanel>().SingleOrDefault(p => p.Selected.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGroupSelectionOnHeader()
|
public void TestGroupSelectionOnHeader()
|
||||||
{
|
{
|
||||||
AddBeatmaps(10, 3);
|
|
||||||
WaitForDrawablePanels();
|
|
||||||
|
|
||||||
SelectNextGroup();
|
SelectNextGroup();
|
||||||
WaitForGroupSelection(0, 0);
|
WaitForGroupSelection(0, 0);
|
||||||
|
|
||||||
SelectPrevPanel();
|
SelectPrevPanel();
|
||||||
|
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
|
||||||
SelectPrevGroup();
|
SelectPrevGroup();
|
||||||
WaitForGroupSelection(2, 9);
|
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestKeyboardSelection()
|
public void TestKeyboardSelection()
|
||||||
{
|
{
|
||||||
AddBeatmaps(10, 3);
|
|
||||||
WaitForDrawablePanels();
|
|
||||||
|
|
||||||
SelectNextPanel();
|
SelectNextPanel();
|
||||||
SelectNextPanel();
|
SelectNextPanel();
|
||||||
SelectNextPanel();
|
SelectNextPanel();
|
@ -6,6 +6,8 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.Select.Filter;
|
||||||
using osu.Game.Screens.SelectV2;
|
using osu.Game.Screens.SelectV2;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -13,8 +15,16 @@ using osuTK.Input;
|
|||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneBeatmapCarouselV2Selection : BeatmapCarouselV2TestScene
|
public partial class TestSceneBeatmapCarouselV2NoGrouping : BeatmapCarouselV2TestScene
|
||||||
{
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
CreateCarousel();
|
||||||
|
SortBy(new FilterCriteria { Sort = SortMode.Title });
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Keyboard selection via up and down arrows doesn't actually change the selection until
|
/// Keyboard selection via up and down arrows doesn't actually change the selection until
|
||||||
/// the select key is pressed.
|
/// the select key is pressed.
|
||||||
@ -79,27 +89,26 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
object? selection = null;
|
object? selection = null;
|
||||||
|
|
||||||
AddStep("store drawable selection", () => selection = getSelectedPanel()?.Item?.Model);
|
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||||
|
|
||||||
CheckHasSelection();
|
CheckHasSelection();
|
||||||
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||||
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
|
||||||
RemoveAllBeatmaps();
|
RemoveAllBeatmaps();
|
||||||
AddUntilStep("no drawable selection", getSelectedPanel, () => Is.Null);
|
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
AddBeatmaps(10);
|
AddBeatmaps(10);
|
||||||
WaitForDrawablePanels();
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
CheckHasSelection();
|
CheckHasSelection();
|
||||||
AddAssert("no drawable selection", getSelectedPanel, () => Is.Null);
|
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||||
|
|
||||||
AddUntilStep("drawable selection restored", () => getSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
AddAssert("drawable selection matches 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);
|
||||||
BeatmapPanel? getSelectedPanel() => Carousel.ChildrenOfType<BeatmapPanel>().SingleOrDefault(p => p.Selected.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -130,6 +139,25 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
WaitForSelection(0, 0);
|
WaitForSelection(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGroupSelectionOnHeader()
|
||||||
|
{
|
||||||
|
AddBeatmaps(10, 3);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForSelection(1, 0);
|
||||||
|
|
||||||
|
SelectPrevPanel();
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForSelection(1, 0);
|
||||||
|
|
||||||
|
SelectPrevPanel();
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForSelection(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestKeyboardSelection()
|
public void TestKeyboardSelection()
|
||||||
{
|
{
|
@ -0,0 +1,65 @@
|
|||||||
|
// 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.Graphics.Primitives;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneBeatmapCarouselV2Scrolling : BeatmapCarouselV2TestScene
|
||||||
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
CreateCarousel();
|
||||||
|
SortBy(new FilterCriteria());
|
||||||
|
|
||||||
|
AddBeatmaps(10);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollPositionMaintainedOnAddSecondSelected()
|
||||||
|
{
|
||||||
|
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<BeatmapPanel>().Single(p => p.Selected.Value)));
|
||||||
|
|
||||||
|
WaitForScrolling();
|
||||||
|
|
||||||
|
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||||
|
|
||||||
|
RemoveFirstBeatmap();
|
||||||
|
WaitForSorting();
|
||||||
|
|
||||||
|
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||||
|
() => Is.EqualTo(positionBefore));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollPositionMaintainedOnAddLastSelected()
|
||||||
|
{
|
||||||
|
Quad positionBefore = default;
|
||||||
|
|
||||||
|
AddStep("scroll to last item", () => Scroll.ScrollToEnd(false));
|
||||||
|
|
||||||
|
AddStep("select last beatmap", () => Carousel.CurrentSelection = BeatmapSets.Last().Beatmaps.Last());
|
||||||
|
|
||||||
|
WaitForScrolling();
|
||||||
|
|
||||||
|
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||||
|
|
||||||
|
RemoveFirstBeatmap();
|
||||||
|
WaitForSorting();
|
||||||
|
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||||
|
() => Is.EqualTo(positionBefore));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -106,11 +106,9 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
private GroupDefinition? lastSelectedGroup;
|
private GroupDefinition? lastSelectedGroup;
|
||||||
private BeatmapInfo? lastSelectedBeatmap;
|
private BeatmapInfo? lastSelectedBeatmap;
|
||||||
|
|
||||||
protected override bool HandleItemSelected(object? model)
|
protected override void HandleItemActivated(CarouselItem item)
|
||||||
{
|
{
|
||||||
base.HandleItemSelected(model);
|
switch (item.Model)
|
||||||
|
|
||||||
switch (model)
|
|
||||||
{
|
{
|
||||||
case GroupDefinition group:
|
case GroupDefinition group:
|
||||||
// Special case – collapsing an open group.
|
// Special case – collapsing an open group.
|
||||||
@ -118,37 +116,42 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
{
|
{
|
||||||
setExpansionStateOfGroup(lastSelectedGroup, false);
|
setExpansionStateOfGroup(lastSelectedGroup, false);
|
||||||
lastSelectedGroup = null;
|
lastSelectedGroup = null;
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setExpandedGroup(group);
|
setExpandedGroup(group);
|
||||||
return false;
|
return;
|
||||||
|
|
||||||
case BeatmapSetInfo setInfo:
|
case BeatmapSetInfo setInfo:
|
||||||
// Selecting a set isn't valid – let's re-select the first difficulty.
|
// Selecting a set isn't valid – let's re-select the first difficulty.
|
||||||
CurrentSelection = setInfo.Beatmaps.First();
|
CurrentSelection = setInfo.Beatmaps.First();
|
||||||
return false;
|
return;
|
||||||
|
|
||||||
case BeatmapInfo beatmapInfo:
|
case BeatmapInfo beatmapInfo:
|
||||||
|
CurrentSelection = beatmapInfo;
|
||||||
// If we have groups, we need to account for them.
|
return;
|
||||||
if (Criteria.SplitOutDifficulties)
|
|
||||||
{
|
|
||||||
// Find the containing group. There should never be too many groups so iterating is efficient enough.
|
|
||||||
GroupDefinition? group = grouping.GroupItems.SingleOrDefault(kvp => kvp.Value.Any(i => ReferenceEquals(i.Model, beatmapInfo))).Key;
|
|
||||||
|
|
||||||
if (group != null)
|
|
||||||
setExpandedGroup(group);
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
protected override void HandleItemSelected(object? model)
|
||||||
{
|
{
|
||||||
|
base.HandleItemSelected(model);
|
||||||
|
|
||||||
|
switch (model)
|
||||||
|
{
|
||||||
|
case BeatmapSetInfo:
|
||||||
|
case GroupDefinition:
|
||||||
|
throw new InvalidOperationException("Groups should never become selected");
|
||||||
|
|
||||||
|
case BeatmapInfo beatmapInfo:
|
||||||
|
// Find any containing group. There should never be too many groups so iterating is efficient enough.
|
||||||
|
GroupDefinition? containingGroup = grouping.GroupItems.SingleOrDefault(kvp => kvp.Value.Any(i => ReferenceEquals(i.Model, beatmapInfo))).Key;
|
||||||
|
|
||||||
|
if (containingGroup != null)
|
||||||
|
setExpandedGroup(containingGroup);
|
||||||
setExpandedSet(beatmapInfo);
|
setExpandedSet(beatmapInfo);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckValidForGroupSelection(CarouselItem item)
|
protected override bool CheckValidForGroupSelection(CarouselItem item)
|
||||||
@ -159,7 +162,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case BeatmapInfo:
|
case BeatmapInfo:
|
||||||
return Criteria.SplitOutDifficulties;
|
return !grouping.BeatmapSetsGroupedTogether;
|
||||||
|
|
||||||
case GroupDefinition:
|
case GroupDefinition:
|
||||||
return false;
|
return false;
|
||||||
@ -180,13 +183,47 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
private void setExpansionStateOfGroup(GroupDefinition group, bool expanded)
|
private void setExpansionStateOfGroup(GroupDefinition group, bool expanded)
|
||||||
{
|
{
|
||||||
if (grouping.GroupItems.TryGetValue(group, out var items))
|
if (grouping.GroupItems.TryGetValue(group, out var items))
|
||||||
|
{
|
||||||
|
if (expanded)
|
||||||
{
|
{
|
||||||
foreach (var i in items)
|
foreach (var i in items)
|
||||||
{
|
{
|
||||||
if (i.Model is GroupDefinition)
|
switch (i.Model)
|
||||||
i.IsExpanded = expanded;
|
{
|
||||||
|
case GroupDefinition:
|
||||||
|
i.IsExpanded = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BeatmapSetInfo set:
|
||||||
|
// Case where there are set headers, header should be visible
|
||||||
|
// and items should use the set's expanded state.
|
||||||
|
i.IsVisible = true;
|
||||||
|
setExpansionStateOfSetItems(set, i.IsExpanded);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Case where there are no set headers, all items should be visible.
|
||||||
|
if (!grouping.BeatmapSetsGroupedTogether)
|
||||||
|
i.IsVisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
i.IsVisible = expanded;
|
{
|
||||||
|
foreach (var i in items)
|
||||||
|
{
|
||||||
|
switch (i.Model)
|
||||||
|
{
|
||||||
|
case GroupDefinition:
|
||||||
|
i.IsExpanded = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
i.IsVisible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
{
|
{
|
||||||
public class BeatmapCarouselFilterGrouping : ICarouselFilter
|
public class BeatmapCarouselFilterGrouping : ICarouselFilter
|
||||||
{
|
{
|
||||||
|
public bool BeatmapSetsGroupedTogether { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection.
|
/// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -36,8 +38,6 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
public async Task<IEnumerable<CarouselItem>> Run(IEnumerable<CarouselItem> items, CancellationToken cancellationToken) => await Task.Run(() =>
|
public async Task<IEnumerable<CarouselItem>> Run(IEnumerable<CarouselItem> items, CancellationToken cancellationToken) => await Task.Run(() =>
|
||||||
{
|
{
|
||||||
bool groupSetsTogether;
|
|
||||||
|
|
||||||
setItems.Clear();
|
setItems.Clear();
|
||||||
groupItems.Clear();
|
groupItems.Clear();
|
||||||
|
|
||||||
@ -48,12 +48,39 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
switch (criteria.Group)
|
switch (criteria.Group)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
groupSetsTogether = true;
|
BeatmapSetsGroupedTogether = true;
|
||||||
newItems.AddRange(items);
|
newItems.AddRange(items);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case GroupMode.Artist:
|
||||||
|
BeatmapSetsGroupedTogether = true;
|
||||||
|
char groupChar = (char)0;
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var b = (BeatmapInfo)item.Model;
|
||||||
|
|
||||||
|
char beatmapFirstChar = char.ToUpperInvariant(b.Metadata.Artist[0]);
|
||||||
|
|
||||||
|
if (beatmapFirstChar > groupChar)
|
||||||
|
{
|
||||||
|
groupChar = beatmapFirstChar;
|
||||||
|
var groupDefinition = new GroupDefinition($"{groupChar}");
|
||||||
|
var groupItem = new CarouselItem(groupDefinition) { DrawHeight = GroupPanel.HEIGHT };
|
||||||
|
|
||||||
|
newItems.Add(groupItem);
|
||||||
|
groupItems[groupDefinition] = new HashSet<CarouselItem> { groupItem };
|
||||||
|
}
|
||||||
|
|
||||||
|
newItems.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case GroupMode.Difficulty:
|
case GroupMode.Difficulty:
|
||||||
groupSetsTogether = false;
|
BeatmapSetsGroupedTogether = false;
|
||||||
int starGroup = int.MinValue;
|
int starGroup = int.MinValue;
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
@ -86,7 +113,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
// Add set headers wherever required.
|
// Add set headers wherever required.
|
||||||
CarouselItem? lastItem = null;
|
CarouselItem? lastItem = null;
|
||||||
|
|
||||||
if (groupSetsTogether)
|
if (BeatmapSetsGroupedTogether)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < newItems.Count; i++)
|
for (int i = 0; i < newItems.Count; i++)
|
||||||
{
|
{
|
||||||
@ -96,7 +123,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
if (item.Model is BeatmapInfo beatmap)
|
if (item.Model is BeatmapInfo beatmap)
|
||||||
{
|
{
|
||||||
bool newBeatmapSet = lastItem == null || (lastItem.Model is BeatmapInfo lastBeatmap && lastBeatmap.BeatmapSet!.ID != beatmap.BeatmapSet!.ID);
|
bool newBeatmapSet = lastItem?.Model is not BeatmapInfo lastBeatmap || lastBeatmap.BeatmapSet!.ID != beatmap.BeatmapSet!.ID;
|
||||||
|
|
||||||
if (newBeatmapSet)
|
if (newBeatmapSet)
|
||||||
{
|
{
|
||||||
|
@ -96,13 +96,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
if (carousel.CurrentSelection != Item!.Model)
|
carousel.Activate(Item!);
|
||||||
{
|
|
||||||
carousel.CurrentSelection = Item!.Model;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
carousel.TryActivateSelection();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -93,7 +92,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
carousel.CurrentSelection = Item!.Model;
|
carousel.Activate(Item!);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,8 +107,6 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
public void Activated()
|
public void Activated()
|
||||||
{
|
{
|
||||||
// sets should never be activated.
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Graphics.Cursor;
|
|||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Layout;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -89,26 +90,39 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
public object? CurrentSelection
|
public object? CurrentSelection
|
||||||
{
|
{
|
||||||
get => currentSelection.Model;
|
get => currentSelection.Model;
|
||||||
set => setSelection(value);
|
set
|
||||||
|
{
|
||||||
|
if (currentSelection.Model != value)
|
||||||
|
{
|
||||||
|
HandleItemSelected(value);
|
||||||
|
|
||||||
|
if (currentSelection.Model != null)
|
||||||
|
HandleItemDeselected(currentSelection.Model);
|
||||||
|
|
||||||
|
currentKeyboardSelection = new Selection(value);
|
||||||
|
currentSelection = currentKeyboardSelection;
|
||||||
|
selectionValid.Invalidate();
|
||||||
|
}
|
||||||
|
else if (currentKeyboardSelection.Model != value)
|
||||||
|
{
|
||||||
|
// Even if the current selection matches, let's ensure the keyboard selection is reset
|
||||||
|
// to the newly selected object. This matches user expectations (for now).
|
||||||
|
currentKeyboardSelection = currentSelection;
|
||||||
|
selectionValid.Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Activate the current selection, if a selection exists and matches keyboard selection.
|
/// Activate the specified item.
|
||||||
/// If keyboard selection does not match selection, this will transfer the selection on first invocation.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void TryActivateSelection()
|
/// <param name="item"></param>
|
||||||
|
public void Activate(CarouselItem item)
|
||||||
{
|
{
|
||||||
if (currentSelection.CarouselItem != currentKeyboardSelection.CarouselItem)
|
(GetMaterialisedDrawableForItem(item) as ICarouselPanel)?.Activated();
|
||||||
{
|
HandleItemActivated(item);
|
||||||
CurrentSelection = currentKeyboardSelection.Model;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSelection.CarouselItem != null)
|
selectionValid.Invalidate();
|
||||||
{
|
|
||||||
(GetMaterialisedDrawableForItem(currentSelection.CarouselItem) as ICarouselPanel)?.Activated();
|
|
||||||
HandleItemActivated(currentSelection.CarouselItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -176,30 +190,28 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
protected virtual bool CheckValidForGroupSelection(CarouselItem item) => true;
|
protected virtual bool CheckValidForGroupSelection(CarouselItem item) => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when an item is "selected".
|
/// Called after an item becomes the <see cref="CurrentSelection"/>.
|
||||||
|
/// Should be used to handle any group expansion, item visibility changes, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Whether the item should be selected.</returns>
|
protected virtual void HandleItemSelected(object? model) { }
|
||||||
protected virtual bool HandleItemSelected(object? model) => true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when an item is "deselected".
|
/// Called when the <see cref="CurrentSelection"/> changes to a new selection.
|
||||||
|
/// Should be used to handle any group expansion, item visibility changes, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void HandleItemDeselected(object? model)
|
protected virtual void HandleItemDeselected(object? model) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when an item is "activated".
|
/// Called when an item is activated via user input (keyboard traversal or a mouse click).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// An activated item should for instance:
|
/// An activated item should decide to perform an action, such as:
|
||||||
/// - Open or close a folder
|
/// - Change its expanded state (and show / hide children items).
|
||||||
/// - Start gameplay on a beatmap difficulty.
|
/// - Set the item to the <see cref="CurrentSelection"/>.
|
||||||
|
/// - Start gameplay on a beatmap difficulty if already selected.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="item">The carousel item which was activated.</param>
|
/// <param name="item">The carousel item which was activated.</param>
|
||||||
protected virtual void HandleItemActivated(CarouselItem item)
|
protected virtual void HandleItemActivated(CarouselItem item) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -312,7 +324,8 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case GlobalAction.Select:
|
case GlobalAction.Select:
|
||||||
TryActivateSelection();
|
if (currentKeyboardSelection.CarouselItem != null)
|
||||||
|
Activate(currentKeyboardSelection.CarouselItem);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.SelectNext:
|
case GlobalAction.SelectNext:
|
||||||
@ -381,32 +394,29 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
// If the user has a different keyboard selection and requests
|
// If the user has a different keyboard selection and requests
|
||||||
// group selection, first transfer the keyboard selection to actual selection.
|
// group selection, first transfer the keyboard selection to actual selection.
|
||||||
if (currentSelection.CarouselItem != currentKeyboardSelection.CarouselItem)
|
if (currentKeyboardSelection.CarouselItem != null && currentSelection.CarouselItem != currentKeyboardSelection.CarouselItem)
|
||||||
{
|
{
|
||||||
TryActivateSelection();
|
Activate(currentKeyboardSelection.CarouselItem);
|
||||||
|
|
||||||
// There's a chance this couldn't resolve, at which point continue with standard traversal.
|
|
||||||
if (currentSelection.CarouselItem == currentKeyboardSelection.CarouselItem)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int originalIndex;
|
int originalIndex;
|
||||||
int newIndex;
|
int newIndex;
|
||||||
|
|
||||||
if (currentSelection.Index == null)
|
if (currentKeyboardSelection.Index == null)
|
||||||
{
|
{
|
||||||
// If there's no current selection, start from either end of the full list.
|
// If there's no current selection, start from either end of the full list.
|
||||||
newIndex = originalIndex = direction > 0 ? carouselItems.Count - 1 : 0;
|
newIndex = originalIndex = direction > 0 ? carouselItems.Count - 1 : 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newIndex = originalIndex = currentSelection.Index.Value;
|
newIndex = originalIndex = currentKeyboardSelection.Index.Value;
|
||||||
|
|
||||||
// As a second special case, if we're group selecting backwards and the current selection isn't a group,
|
// As a second special case, if we're group selecting backwards and the current selection isn't a group,
|
||||||
// make sure to go back to the group header this item belongs to, so that the block below doesn't find it and stop too early.
|
// make sure to go back to the group header this item belongs to, so that the block below doesn't find it and stop too early.
|
||||||
if (direction < 0)
|
if (direction < 0)
|
||||||
{
|
{
|
||||||
while (!CheckValidForGroupSelection(carouselItems[newIndex]))
|
while (newIndex > 0 && !CheckValidForGroupSelection(carouselItems[newIndex]))
|
||||||
newIndex--;
|
newIndex--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,7 +430,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
if (CheckValidForGroupSelection(newItem))
|
if (CheckValidForGroupSelection(newItem))
|
||||||
{
|
{
|
||||||
setSelection(newItem.Model);
|
HandleItemActivated(newItem);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} while (newIndex != originalIndex);
|
} while (newIndex != originalIndex);
|
||||||
@ -435,23 +445,6 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
private Selection currentKeyboardSelection = new Selection();
|
private Selection currentKeyboardSelection = new Selection();
|
||||||
private Selection currentSelection = new Selection();
|
private Selection currentSelection = new Selection();
|
||||||
|
|
||||||
private void setSelection(object? model)
|
|
||||||
{
|
|
||||||
if (currentSelection.Model == model)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (HandleItemSelected(model))
|
|
||||||
{
|
|
||||||
if (currentSelection.Model != null)
|
|
||||||
HandleItemDeselected(currentSelection.Model);
|
|
||||||
|
|
||||||
currentKeyboardSelection = new Selection(model);
|
|
||||||
currentSelection = currentKeyboardSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectionValid.Invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setKeyboardSelection(object? model)
|
private void setKeyboardSelection(object? model)
|
||||||
{
|
{
|
||||||
currentKeyboardSelection = new Selection(model);
|
currentKeyboardSelection = new Selection(model);
|
||||||
@ -687,6 +680,15 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
carouselPanel.Expanded.Value = false;
|
carouselPanel.Expanded.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||||
|
{
|
||||||
|
// handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed).
|
||||||
|
if (invalidation.HasFlag(Invalidation.DrawSize))
|
||||||
|
selectionValid.Invalidate();
|
||||||
|
|
||||||
|
return base.OnInvalidate(invalidation, source);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Internal helper classes
|
#region Internal helper classes
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -106,7 +105,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
carousel.CurrentSelection = Item!.Model;
|
carousel.Activate(Item!);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,8 +120,6 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
public void Activated()
|
public void Activated()
|
||||||
{
|
{
|
||||||
// sets should never be activated.
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
Loading…
Reference in New Issue
Block a user