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:
commit
1afd1f5000
@ -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()
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -300,5 +300,5 @@ namespace osu.Game.Screens.SelectV2
|
||||
#endregion
|
||||
}
|
||||
|
||||
public record GroupDefinition(string Title);
|
||||
public record GroupDefinition(object Data, string Title);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user