1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-08 11:42:55 +08:00

Add initial difficulty grouping support

This commit is contained in:
Dean Herbert 2025-01-24 19:29:14 +09:00
parent 3cde11ab77
commit d5dc55149d
No known key found for this signature in database
3 changed files with 191 additions and 17 deletions

View File

@ -92,34 +92,56 @@ namespace osu.Game.Screens.SelectV2
#region Selection handling
private GroupDefinition? lastSelectedGroup;
private BeatmapInfo? lastSelectedBeatmap;
protected override void HandleItemSelected(object? model)
{
base.HandleItemSelected(model);
// Selecting a set isn't valid let's re-select the first difficulty.
if (model is BeatmapSetInfo setInfo)
switch (model)
{
CurrentSelection = setInfo.Beatmaps.First();
return;
}
case GroupDefinition group:
if (lastSelectedGroup != null)
setVisibilityOfGroupItems(lastSelectedGroup, false);
lastSelectedGroup = group;
if (model is BeatmapInfo beatmapInfo)
setVisibilityOfSetItems(beatmapInfo.BeatmapSet!, true);
setVisibilityOfGroupItems(group, true);
// In stable, you can kinda select a group (expand without changing selection)
// For simplicity, let's not do that for now and handle similar to a beatmap set header.
CurrentSelection = grouping.GroupItems[group].First().Model;
return;
case BeatmapSetInfo setInfo:
// Selecting a set isn't valid let's re-select the first difficulty.
CurrentSelection = setInfo.Beatmaps.First();
return;
case BeatmapInfo beatmapInfo:
if (lastSelectedBeatmap != null)
setVisibilityOfSetItems(lastSelectedBeatmap.BeatmapSet!, false);
lastSelectedBeatmap = beatmapInfo;
setVisibilityOfSetItems(beatmapInfo.BeatmapSet!, true);
break;
}
}
protected override void HandleItemDeselected(object? model)
private void setVisibilityOfGroupItems(GroupDefinition group, bool visible)
{
base.HandleItemDeselected(model);
if (model is BeatmapInfo beatmapInfo)
setVisibilityOfSetItems(beatmapInfo.BeatmapSet!, false);
if (grouping.GroupItems.TryGetValue(group, out var items))
{
foreach (var i in items)
i.IsVisible = visible;
}
}
private void setVisibilityOfSetItems(BeatmapSetInfo set, bool visible)
{
if (grouping.SetItems.TryGetValue(set, out var group))
if (grouping.SetItems.TryGetValue(set, out var items))
{
foreach (var i in group)
foreach (var i in items)
i.IsVisible = visible;
}
}
@ -143,9 +165,11 @@ namespace osu.Game.Screens.SelectV2
private readonly DrawablePool<BeatmapPanel> beatmapPanelPool = new DrawablePool<BeatmapPanel>(100);
private readonly DrawablePool<BeatmapSetPanel> setPanelPool = new DrawablePool<BeatmapSetPanel>(100);
private readonly DrawablePool<GroupPanel> groupPanelPool = new DrawablePool<GroupPanel>(100);
private void setupPools()
{
AddInternal(groupPanelPool);
AddInternal(beatmapPanelPool);
AddInternal(setPanelPool);
}
@ -154,7 +178,12 @@ namespace osu.Game.Screens.SelectV2
{
switch (item.Model)
{
case GroupDefinition:
return groupPanelPool.Get();
case BeatmapInfo:
// TODO: if beatmap is a group selection target, it needs to be a different drawable
// with more information attached.
return beatmapPanelPool.Get();
case BeatmapSetInfo:
@ -166,4 +195,6 @@ namespace osu.Game.Screens.SelectV2
#endregion
}
public record GroupDefinition(string Title);
}

View File

@ -18,7 +18,13 @@ namespace osu.Game.Screens.SelectV2
/// </summary>
public IDictionary<BeatmapSetInfo, HashSet<CarouselItem>> SetItems => setItems;
/// <summary>
/// Groups contain children which are group-selectable. This dictionary holds the relationships between groups-panels to allow expanding them on selection.
/// </summary>
public IDictionary<GroupDefinition, HashSet<CarouselItem>> GroupItems => groupItems;
private readonly Dictionary<BeatmapSetInfo, HashSet<CarouselItem>> setItems = new Dictionary<BeatmapSetInfo, HashSet<CarouselItem>>();
private readonly Dictionary<GroupDefinition, HashSet<CarouselItem>> groupItems = new Dictionary<GroupDefinition, HashSet<CarouselItem>>();
private readonly Func<FilterCriteria> getCriteria;
@ -31,15 +37,40 @@ namespace osu.Game.Screens.SelectV2
{
var criteria = getCriteria();
int starGroup = int.MinValue;
if (criteria.SplitOutDifficulties)
{
var diffItems = new List<CarouselItem>(items.Count());
GroupDefinition? group = null;
foreach (var item in items)
{
item.IsVisible = true;
var b = (BeatmapInfo)item.Model;
if (b.StarRating > starGroup)
{
starGroup = (int)Math.Floor(b.StarRating);
group = new GroupDefinition($"{starGroup} - {++starGroup} *");
diffItems.Add(new CarouselItem(group)
{
DrawHeight = GroupPanel.HEIGHT,
IsGroupSelectionTarget = true
});
}
if (!groupItems.TryGetValue(group!, out var related))
groupItems[group!] = related = new HashSet<CarouselItem>();
related.Add(item);
diffItems.Add(item);
item.IsVisible = false;
item.IsGroupSelectionTarget = true;
}
return items;
return diffItems;
}
CarouselItem? lastItem = null;
@ -64,7 +95,6 @@ namespace osu.Game.Screens.SelectV2
if (!setItems.TryGetValue(b.BeatmapSet!, out var related))
setItems[b.BeatmapSet!] = related = new HashSet<CarouselItem>();
related.Add(item);
}

View File

@ -0,0 +1,113 @@
// 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;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.SelectV2
{
public partial class GroupPanel : PoolableDrawable, ICarouselPanel
{
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 2;
[Resolved]
private BeatmapCarousel carousel { get; set; } = null!;
private Box activationFlash = null!;
private OsuSpriteText text = null!;
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(500, HEIGHT);
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue.Darken(5),
Alpha = 0.8f,
RelativeSizeAxes = Axes.Both,
},
activationFlash = new Box
{
Colour = Color4.White,
Blending = BlendingParameters.Additive,
Alpha = 0,
RelativeSizeAxes = Axes.Both,
},
text = new OsuSpriteText
{
Padding = new MarginPadding(5),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
}
};
Selected.BindValueChanged(value =>
{
activationFlash.FadeTo(value.NewValue ? 0.2f : 0, 500, Easing.OutQuint);
});
KeyboardSelected.BindValueChanged(value =>
{
if (value.NewValue)
{
BorderThickness = 5;
BorderColour = Color4.Pink;
}
else
{
BorderThickness = 0;
}
});
}
protected override void PrepareForUse()
{
base.PrepareForUse();
Debug.Assert(Item != null);
Debug.Assert(Item.IsGroupSelectionTarget);
GroupDefinition group = (GroupDefinition)Item.Model;
text.Text = group.Title;
this.FadeInFromZero(500, Easing.OutQuint);
}
protected override bool OnClick(ClickEvent e)
{
carousel.CurrentSelection = Item!.Model;
return true;
}
#region ICarouselPanel
public CarouselItem? Item { get; set; }
public BindableBool Selected { get; } = new BindableBool();
public BindableBool KeyboardSelected { get; } = new BindableBool();
public double DrawYPosition { get; set; }
public void Activated()
{
// sets should never be activated.
throw new InvalidOperationException();
}
#endregion
}
}