mirror of
https://github.com/ppy/osu.git
synced 2025-01-31 14:25:10 +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:
commit
beaadfa1fa
@ -16,6 +16,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
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.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),
|
Padding = new MarginPadding(10),
|
||||||
TextAnchor = Anchor.CentreLeft,
|
TextAnchor = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -258,16 +258,29 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
if (carousel.IsNull())
|
if (carousel.IsNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
stats.Text = $"""
|
stats.Clear();
|
||||||
store
|
createHeader("beatmap store");
|
||||||
sets: {beatmapSets.Count}
|
stats.AddParagraph($"""
|
||||||
beatmaps: {beatmapCount}
|
sets: {beatmapSets.Count}
|
||||||
carousel:
|
beatmaps: {beatmapCount}
|
||||||
sorting: {carousel.IsFiltering}
|
""");
|
||||||
tracked: {carousel.ItemsTracked}
|
createHeader("carousel");
|
||||||
displayable: {carousel.DisplayableItems}
|
stats.AddParagraph($"""
|
||||||
displayed: {carousel.VisibleItems}
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -14,7 +13,6 @@ using osu.Framework.Graphics.Pooling;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Multiplayer;
|
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
namespace osu.Game.Screens.SelectV2
|
namespace osu.Game.Screens.SelectV2
|
||||||
@ -93,14 +91,8 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
public void Filter(FilterCriteria criteria)
|
public void Filter(FilterCriteria criteria)
|
||||||
{
|
{
|
||||||
Criteria = criteria;
|
Criteria = criteria;
|
||||||
FilterAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task FilterAsync()
|
|
||||||
{
|
|
||||||
loading.Show();
|
loading.Show();
|
||||||
await base.FilterAsync().ConfigureAwait(true);
|
FilterAsync().ContinueWith(_ => Schedule(() => loading.Hide()));
|
||||||
loading.Hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class Carousel<T> : CompositeDrawable
|
public abstract partial class Carousel<T> : CompositeDrawable
|
||||||
{
|
{
|
||||||
/// <summary>
|
#region Properties and methods for external usage
|
||||||
/// 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>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it.
|
/// 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>
|
/// <summary>
|
||||||
/// The number of carousel items currently in rotation for display.
|
/// The number of carousel items currently in rotation for display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DisplayableItems => displayedCarouselItems?.Count ?? 0;
|
public int DisplayableItems => carouselItems?.Count ?? 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of items currently actualised into drawables.
|
/// The number of items currently actualised into drawables.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int VisibleItems => scroll.Panels.Count;
|
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>
|
/// <summary>
|
||||||
/// The currently selected model.
|
/// The currently selected model.
|
||||||
/// </summary>
|
/// </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()
|
/// <summary>
|
||||||
{
|
/// A collection of filters which should be run each time a <see cref="FilterAsync"/> is executed.
|
||||||
InternalChild = scroll = new CarouselScrollContainer
|
/// </summary>
|
||||||
{
|
/// <remarks>
|
||||||
RelativeSizeAxes = Axes.Both,
|
/// Implementations should add all required filters as part of their initialisation.
|
||||||
Masking = false,
|
///
|
||||||
};
|
/// 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>
|
/// <summary>
|
||||||
/// Queue an asynchronous filter operation.
|
/// Queue an asynchronous filter operation.
|
||||||
@ -151,8 +150,29 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
/// <returns>A <see cref="CarouselItem"/> representing the model.</returns>
|
/// <returns>A <see cref="CarouselItem"/> representing the model.</returns>
|
||||||
protected abstract CarouselItem CreateCarouselItemForModel(T model);
|
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
|
#region Filtering and display preparation
|
||||||
|
|
||||||
|
private List<CarouselItem>? carouselItems;
|
||||||
|
|
||||||
private Task filterTask = Task.CompletedTask;
|
private Task filterTask = Task.CompletedTask;
|
||||||
private CancellationTokenSource cancellationSource = new CancellationTokenSource();
|
private CancellationTokenSource cancellationSource = new CancellationTokenSource();
|
||||||
|
|
||||||
@ -202,7 +222,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
log("Items ready for display");
|
log("Items ready for display");
|
||||||
displayedCarouselItems = items.ToList();
|
carouselItems = items.ToList();
|
||||||
displayedRange = null;
|
displayedRange = null;
|
||||||
|
|
||||||
updateSelection();
|
updateSelection();
|
||||||
@ -233,9 +253,9 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
{
|
{
|
||||||
currentSelectionCarouselItem = null;
|
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;
|
bool isSelected = item.Model == currentSelection;
|
||||||
|
|
||||||
@ -286,7 +306,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (displayedCarouselItems == null)
|
if (carouselItems == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var range = getDisplayRange();
|
var range = getDisplayRange();
|
||||||
@ -336,15 +356,15 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
private DisplayRange getDisplayRange()
|
private DisplayRange getDisplayRange()
|
||||||
{
|
{
|
||||||
Debug.Assert(displayedCarouselItems != null);
|
Debug.Assert(carouselItems != null);
|
||||||
|
|
||||||
// Find index range of all items that should be on-screen
|
// Find index range of all items that should be on-screen
|
||||||
carouselBoundsItem.CarouselYPosition = visibleUpperBound - DistanceOffscreenToPreload;
|
carouselBoundsItem.CarouselYPosition = visibleUpperBound - DistanceOffscreenToPreload;
|
||||||
int firstIndex = displayedCarouselItems.BinarySearch(carouselBoundsItem);
|
int firstIndex = carouselItems.BinarySearch(carouselBoundsItem);
|
||||||
if (firstIndex < 0) firstIndex = ~firstIndex;
|
if (firstIndex < 0) firstIndex = ~firstIndex;
|
||||||
|
|
||||||
carouselBoundsItem.CarouselYPosition = visibleBottomBound + DistanceOffscreenToPreload;
|
carouselBoundsItem.CarouselYPosition = visibleBottomBound + DistanceOffscreenToPreload;
|
||||||
int lastIndex = displayedCarouselItems.BinarySearch(carouselBoundsItem);
|
int lastIndex = carouselItems.BinarySearch(carouselBoundsItem);
|
||||||
if (lastIndex < 0) lastIndex = ~lastIndex;
|
if (lastIndex < 0) lastIndex = ~lastIndex;
|
||||||
|
|
||||||
firstIndex = Math.Max(0, firstIndex - 1);
|
firstIndex = Math.Max(0, firstIndex - 1);
|
||||||
@ -355,11 +375,11 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
private void updateDisplayedRange(DisplayRange range)
|
private void updateDisplayedRange(DisplayRange range)
|
||||||
{
|
{
|
||||||
Debug.Assert(displayedCarouselItems != null);
|
Debug.Assert(carouselItems != null);
|
||||||
|
|
||||||
List<CarouselItem> toDisplay = range.Last - range.First == 0
|
List<CarouselItem> toDisplay = range.Last - range.First == 0
|
||||||
? new List<CarouselItem>()
|
? 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.
|
// Iterate over all panels which are already displayed and figure which need to be displayed / removed.
|
||||||
foreach (var panel in scroll.Panels)
|
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
|
// 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).
|
// 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));
|
scroll.SetLayoutHeight((float)(lastItem.CarouselYPosition + lastItem.DrawHeight + visibleHalfHeight));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
Loading…
Reference in New Issue
Block a user