1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-22 20:12:56 +08:00

Merge branch 'master' into bss/the-actual-submission

This commit is contained in:
Bartłomiej Dach 2025-02-07 15:30:37 +01:00
commit 1afd1f5000
No known key found for this signature in database
7 changed files with 140 additions and 144 deletions

View File

@ -138,8 +138,8 @@ namespace osu.Game.Tests.Visual.SongSelect
protected void CheckNoSelection() => AddAssert("has no selection", () => Carousel.CurrentSelection, () => Is.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 ICarouselPanel? GetSelectedPanel() => Carousel.ChildrenOfType<ICarouselPanel>().SingleOrDefault(p => p.Selected.Value);
protected ICarouselPanel? GetKeyboardSelectedPanel() => Carousel.ChildrenOfType<ICarouselPanel>().SingleOrDefault(p => p.KeyboardSelected.Value);
protected void WaitForGroupSelection(int group, int panel)
{
@ -215,29 +215,32 @@ namespace osu.Game.Tests.Visual.SongSelect
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++)
{
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);
}
BeatmapSets.Add(CreateTestBeatmapSetInfo(fixedDifficultiesPerSet, randomMetadata));
});
protected static BeatmapSetInfo CreateTestBeatmapSetInfo(int? fixedDifficultiesPerSet, bool randomMetadata)
{
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();
}
return beatmapSetInfo;
}
private static long randomCharPointer;
private static char getRandomCharacter()

View File

@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Task.Run(() =>
{
for (int j = 0; j < count; j++)
generated.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4)));
generated.Add(CreateTestBeatmapSetInfo(3, true));
}).ConfigureAwait(true);
});

View File

@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.SongSelect
RemoveAllBeatmaps();
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
AddBeatmaps(10);
AddBeatmaps(3);
WaitForDrawablePanels();
CheckHasSelection();
@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.SongSelect
}
[Test]
public void TestGroupSelectionOnHeader()
public void TestGroupSelectionOnHeaderKeyboard()
{
SelectNextGroup();
WaitForGroupSelection(0, 0);
@ -114,6 +114,26 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
}
[Test]
public void TestGroupSelectionOnHeaderMouse()
{
SelectNextGroup();
WaitForGroupSelection(0, 0);
AddAssert("keyboard selected panel is beatmap", GetKeyboardSelectedPanel, Is.TypeOf<BeatmapPanel>);
AddAssert("selected panel is beatmap", GetSelectedPanel, Is.TypeOf<BeatmapPanel>);
ClickVisiblePanel<GroupPanel>(0);
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<GroupPanel>);
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
ClickVisiblePanel<GroupPanel>(0);
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<GroupPanel>);
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
AddAssert("selected panel is still beatmap", GetSelectedPanel, Is.TypeOf<BeatmapPanel>);
}
[Test]
public void TestKeyboardSelection()
{

View File

@ -300,5 +300,5 @@ namespace osu.Game.Screens.SelectV2
#endregion
}
public record GroupDefinition(string Title);
public record GroupDefinition(object Data, string Title);
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Game.Beatmaps;
@ -36,137 +35,106 @@ namespace osu.Game.Screens.SelectV2
this.getCriteria = getCriteria;
}
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)
{
setItems.Clear();
groupItems.Clear();
return await Task.Run(() =>
{
setItems.Clear();
groupItems.Clear();
var criteria = getCriteria();
var newItems = new List<CarouselItem>(items.Count());
var criteria = getCriteria();
var newItems = new List<CarouselItem>();
// Add criteria groups.
BeatmapInfo? lastBeatmap = null;
GroupDefinition? lastGroup = null;
HashSet<CarouselItem>? currentGroupItems = null;
HashSet<CarouselItem>? currentSetItems = null;
BeatmapSetsGroupedTogether = criteria.Group != GroupMode.Difficulty;
foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
var beatmap = (BeatmapInfo)item.Model;
if (createGroupIfRequired(criteria, beatmap, lastGroup) is GroupDefinition newGroup)
{
// When reaching a new group, ensure we reset any beatmap set tracking.
currentSetItems = null;
lastBeatmap = null;
groupItems[newGroup] = currentGroupItems = new HashSet<CarouselItem>();
lastGroup = newGroup;
addItem(new CarouselItem(newGroup)
{
DrawHeight = GroupPanel.HEIGHT,
DepthLayer = -2,
});
}
if (BeatmapSetsGroupedTogether)
{
bool newBeatmapSet = lastBeatmap?.BeatmapSet!.ID != beatmap.BeatmapSet!.ID;
if (newBeatmapSet)
{
setItems[beatmap.BeatmapSet!] = currentSetItems = new HashSet<CarouselItem>();
addItem(new CarouselItem(beatmap.BeatmapSet!)
{
DrawHeight = BeatmapSetPanel.HEIGHT,
DepthLayer = -1
});
}
}
addItem(item);
lastBeatmap = beatmap;
void addItem(CarouselItem i)
{
newItems.Add(i);
currentGroupItems?.Add(i);
currentSetItems?.Add(i);
i.IsVisible = i.Model is GroupDefinition || (lastGroup == null && (i.Model is BeatmapSetInfo || currentSetItems == null));
}
}
return newItems;
}, cancellationToken).ConfigureAwait(false);
}
private GroupDefinition? createGroupIfRequired(FilterCriteria criteria, BeatmapInfo beatmap, GroupDefinition? lastGroup)
{
switch (criteria.Group)
{
default:
BeatmapSetsGroupedTogether = true;
newItems.AddRange(items);
break;
case GroupMode.Artist:
BeatmapSetsGroupedTogether = true;
char groupChar = (char)0;
char groupChar = lastGroup?.Data as char? ?? (char)0;
char beatmapFirstChar = char.ToUpperInvariant(beatmap.Metadata.Artist[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);
}
if (beatmapFirstChar > groupChar)
return new GroupDefinition(beatmapFirstChar, $"{beatmapFirstChar}");
break;
case GroupMode.Difficulty:
BeatmapSetsGroupedTogether = false;
int starGroup = int.MinValue;
int starGroup = lastGroup?.Data as int? ?? -1;
foreach (var item in items)
if (beatmap.StarRating > starGroup)
{
cancellationToken.ThrowIfCancellationRequested();
var b = (BeatmapInfo)item.Model;
if (b.StarRating > starGroup)
{
starGroup = (int)Math.Floor(b.StarRating);
var groupDefinition = new GroupDefinition($"{starGroup} - {++starGroup} *");
var groupItem = new CarouselItem(groupDefinition)
{
DrawHeight = GroupPanel.HEIGHT,
DepthLayer = -2
};
newItems.Add(groupItem);
groupItems[groupDefinition] = new HashSet<CarouselItem> { groupItem };
}
newItems.Add(item);
starGroup = (int)Math.Floor(beatmap.StarRating);
return new GroupDefinition(starGroup + 1, $"{starGroup} - {starGroup + 1} *");
}
break;
}
// Add set headers wherever required.
CarouselItem? lastItem = null;
if (BeatmapSetsGroupedTogether)
{
for (int i = 0; i < newItems.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var item = newItems[i];
if (item.Model is BeatmapInfo beatmap)
{
bool newBeatmapSet = lastItem?.Model is not BeatmapInfo lastBeatmap || lastBeatmap.BeatmapSet!.ID != beatmap.BeatmapSet!.ID;
if (newBeatmapSet)
{
var setItem = new CarouselItem(beatmap.BeatmapSet!)
{
DrawHeight = BeatmapSetPanel.HEIGHT,
DepthLayer = -1
};
setItems[beatmap.BeatmapSet!] = new HashSet<CarouselItem> { setItem };
newItems.Insert(i, setItem);
i++;
}
setItems[beatmap.BeatmapSet!].Add(item);
item.IsVisible = false;
}
lastItem = item;
}
}
// Link group items to their headers.
GroupDefinition? lastGroup = null;
foreach (var item in newItems)
{
cancellationToken.ThrowIfCancellationRequested();
if (item.Model is GroupDefinition group)
{
lastGroup = group;
continue;
}
if (lastGroup != null)
{
groupItems[lastGroup].Add(item);
item.IsVisible = false;
}
}
return newItems;
}, cancellationToken).ConfigureAwait(false);
return null;
}
}
}

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.SelectV2
switch (criteria.Sort)
{
case SortMode.Artist:
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.BeatmapSet!.Metadata.Artist, bb.BeatmapSet!.Metadata.Artist);
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.Metadata.Artist, bb.Metadata.Artist);
if (comparison == 0)
goto case SortMode.Title;
break;
@ -46,7 +46,7 @@ namespace osu.Game.Screens.SelectV2
break;
case SortMode.Title:
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.BeatmapSet!.Metadata.Title, bb.BeatmapSet!.Metadata.Title);
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.Metadata.Title, bb.Metadata.Title);
break;
default:

View File

@ -119,6 +119,11 @@ namespace osu.Game.Screens.SelectV2
/// <param name="item"></param>
public void Activate(CarouselItem item)
{
// Regardless of how the item handles activation, update keyboard selection to the activated panel.
// In other words, when a panel is clicked, keyboard selection should default to matching the clicked
// item.
setKeyboardSelection(item.Model);
(GetMaterialisedDrawableForItem(item) as ICarouselPanel)?.Activated();
HandleItemActivated(item);