From 3dcbcb5f642da93009c4991ee102ec348583b633 Mon Sep 17 00:00:00 2001 From: tadatomix Date: Tue, 7 Oct 2025 01:06:46 +0300 Subject: [PATCH] Make initial work to create a ranked status style --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 13 ++ .../SelectV2/BeatmapCarouselFilterGrouping.cs | 17 +- .../SelectV2/PanelGroupRankedStatus.cs | 200 ++++++++++++++++++ 3 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Screens/SelectV2/PanelGroupRankedStatus.cs diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 93356fef92..df8d6e7215 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -840,9 +840,11 @@ namespace osu.Game.Screens.SelectV2 private readonly DrawablePool groupPanelPool = new DrawablePool(100); private readonly DrawablePool starsGroupPanelPool = new DrawablePool(11); private readonly DrawablePool ranksGroupPanelPool = new DrawablePool(9); + private readonly DrawablePool statusGroupPanelPool = new DrawablePool(9); private void setupPools() { + AddInternal(statusGroupPanelPool); AddInternal(ranksGroupPanelPool); AddInternal(starsGroupPanelPool); AddInternal(groupPanelPool); @@ -880,6 +882,9 @@ namespace osu.Game.Screens.SelectV2 if (x is RankDisplayGroupDefinition rankX && y is RankDisplayGroupDefinition rankY) return rankX.Equals(rankY); + if (x is RankedStatusGroupDefinition statusX && y is RankedStatusGroupDefinition statusY) + return statusX.Equals(statusY); + return base.CheckModelEquality(x, y); } @@ -887,6 +892,9 @@ namespace osu.Game.Screens.SelectV2 { switch (item.Model) { + case RankedStatusGroupDefinition: + return statusGroupPanelPool.Get(); + case StarDifficultyGroupDefinition: return starsGroupPanelPool.Get(); @@ -1154,6 +1162,11 @@ namespace osu.Game.Screens.SelectV2 /// public record RankDisplayGroupDefinition(ScoreRank Rank) : GroupDefinition(-(int)Rank, Rank.GetLocalisableDescription()); + /// + /// Defines a grouping header for a set of carousel items grouped by ranked status. + /// + public record RankedStatusGroupDefinition(int Order, BeatmapOnlineStatus Status) : GroupDefinition(Order, Status.GetLocalisableDescription()); + /// /// Used to represent a portion of a under a . /// The purpose of this model is to support splitting beatmap sets apart when the active grouping mode demands it. diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 0b7e29c363..378e688738 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Collections; @@ -315,28 +314,28 @@ namespace osu.Game.Screens.SelectV2 { case BeatmapOnlineStatus.Ranked: case BeatmapOnlineStatus.Approved: - return new GroupDefinition(0, BeatmapOnlineStatus.Ranked.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(0, BeatmapOnlineStatus.Ranked).Yield(); case BeatmapOnlineStatus.Qualified: - return new GroupDefinition(1, status.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(1, status).Yield(); case BeatmapOnlineStatus.WIP: - return new GroupDefinition(2, status.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(2, status).Yield(); case BeatmapOnlineStatus.Pending: - return new GroupDefinition(3, status.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(3, status).Yield(); case BeatmapOnlineStatus.Graveyard: - return new GroupDefinition(4, status.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(4, status).Yield(); case BeatmapOnlineStatus.LocallyModified: - return new GroupDefinition(5, status.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(5, status).Yield(); case BeatmapOnlineStatus.None: - return new GroupDefinition(6, status.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(6, status).Yield(); case BeatmapOnlineStatus.Loved: - return new GroupDefinition(7, status.GetDescription()).Yield(); + return new RankedStatusGroupDefinition(7, status).Yield(); default: throw new ArgumentOutOfRangeException(nameof(status), status, null); diff --git a/osu.Game/Screens/SelectV2/PanelGroupRankedStatus.cs b/osu.Game/Screens/SelectV2/PanelGroupRankedStatus.cs new file mode 100644 index 0000000000..b8dfc73bd6 --- /dev/null +++ b/osu.Game/Screens/SelectV2/PanelGroupRankedStatus.cs @@ -0,0 +1,200 @@ +// Copyright (c) ppy Pty Ltd . 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.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; +using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class PanelGroupRankedStatus : Panel + { + public const float HEIGHT = PanelGroup.HEIGHT; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private Drawable iconContainer = null!; + private Box backgroundBorder = null!; + private Box contentBackground = null!; + private OsuSpriteText starRatingText = null!; + private CircularContainer countPill = null!; + private OsuSpriteText countText = null!; + private TrianglesV2 triangles = null!; + private Box glow = null!; + + [BackgroundDependencyLoader] + private void load() + { + Height = PanelGroup.HEIGHT; + + Icon = iconContainer = new Container + { + AlwaysPresent = true, + RelativeSizeAxes = Axes.Y, + Alpha = 0f, + Child = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ChevronDown, + Size = new Vector2(12), + }, + }; + + Background = backgroundBorder = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Highlight1, + }; + + AccentColour = colourProvider.Highlight1; + Content.Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + triangles = new TrianglesV2 + { + RelativeSizeAxes = Axes.Both, + Thickness = 0.02f, + SpawnRatio = 0.6f, + Colour = ColourInfo.GradientHorizontal(colourProvider.Background6, colourProvider.Background5) + }, + glow = new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Colour = ColourInfo.GradientHorizontal(colourProvider.Highlight1, colourProvider.Highlight1.Opacity(0f)), + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f, 0f), + Margin = new MarginPadding { Left = 10f }, + Children = new Drawable[] + { + starRatingText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + UseFullGlyphHeight = false, + Font = OsuFont.Style.Heading2, + } + } + }, + countPill = new CircularContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(50f, 14f), + Margin = new MarginPadding { Right = 30f }, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Style.Caption1.With(weight: FontWeight.Bold), + UseFullGlyphHeight = false, + } + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(_ => onExpanded(), true); + } + + private Color4 statusColour; + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + Debug.Assert(Item != null); + + var group = (RankedStatusGroupDefinition)Item.Model; + BeatmapOnlineStatus status = group.Status; + + statusColour = OsuColour.ForBeatmapSetOnlineStatus(status) ?? Color4.White; + + AccentColour = statusColour; + backgroundBorder.Colour = statusColour; + contentBackground.Colour = statusColour.Darken(1f); + glow.Colour = ColourInfo.GradientHorizontal(statusColour, statusColour.Opacity(0f)); + + starRatingText.Text = group.Title; + + ColourInfo colour = ColourInfo.GradientHorizontal(statusColour.Darken(0.6f), statusColour.Darken(0.8f)); + + triangles.Colour = colour; + + countText.Text = Item.NestedItemCount.ToLocalisableString(@"N0"); + + onExpanded(); + } + + private void onExpanded() + { + const float duration = 500; + + iconContainer.ResizeWidthTo(Expanded.Value ? 20f : 5f, duration, Easing.OutQuint); + iconContainer.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint); + + glow.FadeTo(Expanded.Value ? 0.4f : 0, duration, Easing.OutQuint); + } + + protected override void Update() + { + base.Update(); + + // Move the count pill in the opposite direction to keep it pinned to the screen regardless of the X position of TopLevelContent. + countPill.X = -TopLevelContent.X; + } + + public override MenuItem[] ContextMenuItems + { + get + { + if (Item == null) + return Array.Empty(); + + return new MenuItem[] + { + new OsuMenuItem(Expanded.Value ? WebCommonStrings.ButtonsCollapse.ToSentence() : WebCommonStrings.ButtonsExpand.ToSentence(), MenuItemType.Highlighted, () => TriggerClick()) + }; + } + } + } +}