1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-03 22:43:04 +08:00

Make CarouselItem sealed and remove BeatmapCarouselItem concept

Less abstraction is better. As far as I can tell, we don't need a custom
model for this. If there's any tracking to be done, it should be done
within `BeatmapCarousel`'s implementation (or a filter).
This commit is contained in:
Dean Herbert 2025-01-23 16:09:18 +09:00
parent c67c0a7fc0
commit 980f6cf18e
No known key found for this signature in database
5 changed files with 99 additions and 110 deletions

View File

@ -181,15 +181,15 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("visual item added", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Count(), () => Is.GreaterThan(0));
AddStep("select middle beatmap", () => carousel.CurrentSelection = beatmapSets.ElementAt(beatmapSets.Count - 2));
AddStep("scroll to selected item", () => scroll.ScrollTo(scroll.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Item!.Selected.Value)));
AddStep("scroll to selected item", () => scroll.ScrollTo(scroll.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Selected.Value)));
AddUntilStep("wait for scroll finished", () => scroll.Current, () => Is.EqualTo(scroll.Target));
AddStep("save selected screen position", () => positionBefore = carousel.ChildrenOfType<BeatmapCarouselPanel>().FirstOrDefault(p => p.Item!.Selected.Value)!.ScreenSpaceDrawQuad);
AddStep("save selected screen position", () => positionBefore = carousel.ChildrenOfType<BeatmapCarouselPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
AddStep("remove first beatmap", () => beatmapSets.Remove(beatmapSets.Last()));
AddUntilStep("sorting finished", () => carousel.IsFiltering, () => Is.False);
AddAssert("select screen position unchanged", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Item!.Selected.Value).ScreenSpaceDrawQuad,
AddAssert("select screen position unchanged", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
() => Is.EqualTo(positionBefore));
}
@ -212,11 +212,11 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for scroll finished", () => scroll.Current, () => Is.EqualTo(scroll.Target));
AddStep("save selected screen position", () => positionBefore = carousel.ChildrenOfType<BeatmapCarouselPanel>().FirstOrDefault(p => p.Item!.Selected.Value)!.ScreenSpaceDrawQuad);
AddStep("save selected screen position", () => positionBefore = carousel.ChildrenOfType<BeatmapCarouselPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
AddStep("remove first beatmap", () => beatmapSets.Remove(beatmapSets.Last()));
AddUntilStep("sorting finished", () => carousel.IsFiltering, () => Is.False);
AddAssert("select screen position unchanged", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Item!.Selected.Value).ScreenSpaceDrawQuad,
AddAssert("select screen position unchanged", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
() => Is.EqualTo(positionBefore));
}

View File

@ -28,37 +28,32 @@ namespace osu.Game.Screens.SelectV2
return items.OrderDescending(Comparer<CarouselItem>.Create((a, b) =>
{
int comparison = 0;
int comparison;
if (a.Model is BeatmapInfo ab && b.Model is BeatmapInfo bb)
var ab = (BeatmapInfo)a.Model;
var bb = (BeatmapInfo)b.Model;
switch (criteria.Sort)
{
switch (criteria.Sort)
{
case SortMode.Artist:
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.BeatmapSet!.Metadata.Artist, bb.BeatmapSet!.Metadata.Artist);
if (comparison == 0)
goto case SortMode.Title;
break;
case SortMode.Artist:
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.BeatmapSet!.Metadata.Artist, bb.BeatmapSet!.Metadata.Artist);
if (comparison == 0)
goto case SortMode.Title;
break;
case SortMode.Difficulty:
comparison = ab.StarRating.CompareTo(bb.StarRating);
break;
case SortMode.Difficulty:
comparison = ab.StarRating.CompareTo(bb.StarRating);
break;
case SortMode.Title:
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.BeatmapSet!.Metadata.Title, bb.BeatmapSet!.Metadata.Title);
break;
case SortMode.Title:
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(ab.BeatmapSet!.Metadata.Title, bb.BeatmapSet!.Metadata.Title);
break;
default:
throw new ArgumentOutOfRangeException();
}
default:
throw new ArgumentOutOfRangeException();
}
if (comparison != 0) return comparison;
if (a is BeatmapCarouselItem aItem && b is BeatmapCarouselItem bItem)
return aItem.ID.CompareTo(bItem.ID);
return 0;
return comparison;
}));
}, cancellationToken).ConfigureAwait(false);
}

View File

@ -1,48 +0,0 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Database;
namespace osu.Game.Screens.SelectV2
{
public class BeatmapCarouselItem : CarouselItem
{
public readonly Guid ID;
/// <summary>
/// Whether this item has a header providing extra information for it.
/// When displaying items which don't have header, we should make sure enough information is included inline.
/// </summary>
public bool HasGroupHeader { get; set; }
/// <summary>
/// Whether this item is a group header.
/// Group headers are generally larger in display. Setting this will account for the size difference.
/// </summary>
public bool IsGroupHeader { get; set; }
public override float DrawHeight => IsGroupHeader ? 80 : 40;
public BeatmapCarouselItem(object model)
: base(model)
{
ID = (Model as IHasGuidPrimaryKey)?.ID ?? Guid.NewGuid();
}
public override string? ToString()
{
switch (Model)
{
case BeatmapInfo bi:
return $"Difficulty: {bi.DifficultyName} ({bi.StarRating:N1}*)";
case BeatmapSetInfo si:
return $"{si.Metadata}";
}
return Model.ToString();
}
}
}

View File

@ -21,27 +21,41 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private BeatmapCarousel carousel { get; set; } = null!;
public CarouselItem? Item
{
get => item;
set
{
item = value;
selected.UnbindBindings();
if (item != null)
selected.BindTo(item.Selected);
}
}
private readonly BindableBool selected = new BindableBool();
private CarouselItem? item;
private Box activationFlash = null!;
private Box background = null!;
private OsuSpriteText text = null!;
[BackgroundDependencyLoader]
private void load()
{
selected.BindValueChanged(value =>
InternalChildren = new Drawable[]
{
background = new Box
{
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)
{
@ -59,6 +73,8 @@ namespace osu.Game.Screens.SelectV2
{
base.FreeAfterUse();
Item = null;
Selected.Value = false;
KeyboardSelected.Value = false;
}
protected override void PrepareForUse()
@ -72,31 +88,44 @@ namespace osu.Game.Screens.SelectV2
Size = new Vector2(500, Item.DrawHeight);
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
Colour = (Item.Model is BeatmapInfo ? Color4.Aqua : Color4.Yellow).Darken(5),
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = Item.ToString() ?? string.Empty,
Padding = new MarginPadding(5),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
}
};
background.Colour = (Item.Model is BeatmapInfo ? Color4.Aqua : Color4.Yellow).Darken(5);
text.Text = getTextFor(Item.Model);
this.FadeInFromZero(500, Easing.OutQuint);
}
private string getTextFor(object item)
{
switch (item)
{
case BeatmapInfo bi:
return $"Difficulty: {bi.DifficultyName} ({bi.StarRating:N1}*)";
case BeatmapSetInfo si:
return $"{si.Metadata}";
}
return "unknown";
}
protected override bool OnClick(ClickEvent e)
{
carousel.CurrentSelection = Item!.Model;
if (carousel.CurrentSelection == Item!.Model)
carousel.ActivateSelection();
else
carousel.CurrentSelection = Item!.Model;
return true;
}
public CarouselItem? Item { get; set; }
public BindableBool Selected { get; } = new BindableBool();
public BindableBool KeyboardSelected { get; } = new BindableBool();
public double DrawYPosition { get; set; }
public void FlashFromActivation()
{
activationFlash.FadeOutFromOne(500, Easing.OutQuint);
}
}
}

View File

@ -1,6 +1,7 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
namespace osu.Game.Screens.SelectV2
@ -10,6 +11,18 @@ namespace osu.Game.Screens.SelectV2
/// </summary>
public interface ICarouselPanel
{
/// <summary>
/// Whether this item has selection.
/// This is managed by <see cref="Carousel{T}"/> and should not be set manually.
/// </summary>
BindableBool Selected { get; }
/// <summary>
/// Whether this item has keyboard selection.
/// This is managed by <see cref="Carousel{T}"/> and should not be set manually.
/// </summary>
BindableBool KeyboardSelected { get; }
/// <summary>
/// The Y position which should be used for displaying this item within the carousel. This is managed by <see cref="Carousel{T}"/> and should not be set manually.
/// </summary>