1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-31 12:13:00 +08:00

Merge pull request #31632 from peppy/beatmap-carousel-v2-cleanups

Initial carousel additions in preparation for selection logic
This commit is contained in:
Bartłomiej Dach 2025-01-23 10:43:12 +01:00 committed by GitHub
commit beaadfa1fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 58 deletions

View File

@ -16,6 +16,7 @@ using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
@ -117,12 +118,11 @@ namespace osu.Game.Tests.Visual.SongSelect
}
}
},
stats = new OsuTextFlowContainer(cp => cp.Font = FrameworkFont.Regular.With())
stats = new OsuTextFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
TextAnchor = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
};
});
@ -258,16 +258,29 @@ namespace osu.Game.Tests.Visual.SongSelect
if (carousel.IsNull())
return;
stats.Text = $"""
store
sets: {beatmapSets.Count}
beatmaps: {beatmapCount}
carousel:
sorting: {carousel.IsFiltering}
tracked: {carousel.ItemsTracked}
displayable: {carousel.DisplayableItems}
displayed: {carousel.VisibleItems}
""";
stats.Clear();
createHeader("beatmap store");
stats.AddParagraph($"""
sets: {beatmapSets.Count}
beatmaps: {beatmapCount}
""");
createHeader("carousel");
stats.AddParagraph($"""
sorting: {carousel.IsFiltering}
tracked: {carousel.ItemsTracked}
displayable: {carousel.DisplayableItems}
displayed: {carousel.VisibleItems}
selected: {carousel.CurrentSelection}
""");
void createHeader(string text)
{
stats.AddParagraph(string.Empty);
stats.AddParagraph(text, cp =>
{
cp.Font = cp.Font.With(size: 18, weight: FontWeight.Bold);
});
}
}
}
}

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -14,7 +13,6 @@ using osu.Framework.Graphics.Pooling;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Select;
namespace osu.Game.Screens.SelectV2
@ -93,14 +91,8 @@ namespace osu.Game.Screens.SelectV2
public void Filter(FilterCriteria criteria)
{
Criteria = criteria;
FilterAsync().FireAndForget();
}
protected override async Task FilterAsync()
{
loading.Show();
await base.FilterAsync().ConfigureAwait(true);
loading.Hide();
FilterAsync().ContinueWith(_ => Schedule(() => loading.Hide()));
}
}
}

View File

@ -30,10 +30,7 @@ namespace osu.Game.Screens.SelectV2
/// </summary>
public abstract partial class Carousel<T> : CompositeDrawable
{
/// <summary>
/// A collection of filters which should be run each time a <see cref="FilterAsync"/> is executed.
/// </summary>
protected IEnumerable<ICarouselFilter> Filters { get; init; } = Enumerable.Empty<ICarouselFilter>();
#region Properties and methods for external usage
/// <summary>
/// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it.
@ -75,22 +72,13 @@ namespace osu.Game.Screens.SelectV2
/// <summary>
/// The number of carousel items currently in rotation for display.
/// </summary>
public int DisplayableItems => displayedCarouselItems?.Count ?? 0;
public int DisplayableItems => carouselItems?.Count ?? 0;
/// <summary>
/// The number of items currently actualised into drawables.
/// </summary>
public int VisibleItems => scroll.Panels.Count;
/// <summary>
/// All items which are to be considered for display in this carousel.
/// Mutating this list will automatically queue a <see cref="FilterAsync"/>.
/// </summary>
/// <remarks>
/// Note that an <see cref="ICarouselFilter"/> may add new items which are displayed but not tracked in this list.
/// </remarks>
protected readonly BindableList<T> Items = new BindableList<T>();
/// <summary>
/// The currently selected model.
/// </summary>
@ -114,20 +102,31 @@ namespace osu.Game.Screens.SelectV2
}
}
private List<CarouselItem>? displayedCarouselItems;
#endregion
private readonly CarouselScrollContainer scroll;
#region Properties and methods concerning implementations
protected Carousel()
{
InternalChild = scroll = new CarouselScrollContainer
{
RelativeSizeAxes = Axes.Both,
Masking = false,
};
/// <summary>
/// A collection of filters which should be run each time a <see cref="FilterAsync"/> is executed.
/// </summary>
/// <remarks>
/// Implementations should add all required filters as part of their initialisation.
///
/// Importantly, each filter is sequentially run in the order provided.
/// Each filter receives the output of the previous filter.
///
/// A filter may add, mutate or remove items.
/// </remarks>
protected IEnumerable<ICarouselFilter> Filters { get; init; } = Enumerable.Empty<ICarouselFilter>();
Items.BindCollectionChanged((_, _) => FilterAsync());
}
/// <summary>
/// All items which are to be considered for display in this carousel.
/// Mutating this list will automatically queue a <see cref="FilterAsync"/>.
/// </summary>
/// <remarks>
/// Note that an <see cref="ICarouselFilter"/> may add new items which are displayed but not tracked in this list.
/// </remarks>
protected readonly BindableList<T> Items = new BindableList<T>();
/// <summary>
/// Queue an asynchronous filter operation.
@ -151,8 +150,29 @@ namespace osu.Game.Screens.SelectV2
/// <returns>A <see cref="CarouselItem"/> representing the model.</returns>
protected abstract CarouselItem CreateCarouselItemForModel(T model);
#endregion
#region Initialisation
private readonly CarouselScrollContainer scroll;
protected Carousel()
{
InternalChild = scroll = new CarouselScrollContainer
{
RelativeSizeAxes = Axes.Both,
Masking = false,
};
Items.BindCollectionChanged((_, _) => FilterAsync());
}
#endregion
#region Filtering and display preparation
private List<CarouselItem>? carouselItems;
private Task filterTask = Task.CompletedTask;
private CancellationTokenSource cancellationSource = new CancellationTokenSource();
@ -202,7 +222,7 @@ namespace osu.Game.Screens.SelectV2
return;
log("Items ready for display");
displayedCarouselItems = items.ToList();
carouselItems = items.ToList();
displayedRange = null;
updateSelection();
@ -233,9 +253,9 @@ namespace osu.Game.Screens.SelectV2
{
currentSelectionCarouselItem = null;
if (displayedCarouselItems == null) return;
if (carouselItems == null) return;
foreach (var item in displayedCarouselItems)
foreach (var item in carouselItems)
{
bool isSelected = item.Model == currentSelection;
@ -286,7 +306,7 @@ namespace osu.Game.Screens.SelectV2
{
base.Update();
if (displayedCarouselItems == null)
if (carouselItems == null)
return;
var range = getDisplayRange();
@ -336,15 +356,15 @@ namespace osu.Game.Screens.SelectV2
private DisplayRange getDisplayRange()
{
Debug.Assert(displayedCarouselItems != null);
Debug.Assert(carouselItems != null);
// Find index range of all items that should be on-screen
carouselBoundsItem.CarouselYPosition = visibleUpperBound - DistanceOffscreenToPreload;
int firstIndex = displayedCarouselItems.BinarySearch(carouselBoundsItem);
int firstIndex = carouselItems.BinarySearch(carouselBoundsItem);
if (firstIndex < 0) firstIndex = ~firstIndex;
carouselBoundsItem.CarouselYPosition = visibleBottomBound + DistanceOffscreenToPreload;
int lastIndex = displayedCarouselItems.BinarySearch(carouselBoundsItem);
int lastIndex = carouselItems.BinarySearch(carouselBoundsItem);
if (lastIndex < 0) lastIndex = ~lastIndex;
firstIndex = Math.Max(0, firstIndex - 1);
@ -355,11 +375,11 @@ namespace osu.Game.Screens.SelectV2
private void updateDisplayedRange(DisplayRange range)
{
Debug.Assert(displayedCarouselItems != null);
Debug.Assert(carouselItems != null);
List<CarouselItem> toDisplay = range.Last - range.First == 0
? new List<CarouselItem>()
: displayedCarouselItems.GetRange(range.First, range.Last - range.First + 1);
: carouselItems.GetRange(range.First, range.Last - range.First + 1);
// Iterate over all panels which are already displayed and figure which need to be displayed / removed.
foreach (var panel in scroll.Panels)
@ -395,9 +415,9 @@ namespace osu.Game.Screens.SelectV2
// Update the total height of all items (to make the scroll container scrollable through the full height even though
// most items are not displayed / loaded).
if (displayedCarouselItems.Count > 0)
if (carouselItems.Count > 0)
{
var lastItem = displayedCarouselItems[^1];
var lastItem = carouselItems[^1];
scroll.SetLayoutHeight((float)(lastItem.CarouselYPosition + lastItem.DrawHeight + visibleHalfHeight));
}
else