diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 0b9857486a..2b98c61e9b 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var longName = CreateAPIBeatmapSet(Ruleset.Value); longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title"; longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name."; + longName.Source = "wow. even the source field has an impossibly long string in it. this really takes the cake, doesn't it?"; longName.HasExplicitContent = true; longName.TrackId = 444; @@ -254,6 +255,12 @@ namespace osu.Game.Tests.Visual.Beatmaps createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); } + [Test] + public void TestExtra() + { + createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo)); + } + [Test] public void TestHoverState() { diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 1e24501426..a3b4c4c86c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -5,109 +5,68 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osuTK; using osu.Game.Resources.Localisation.Web; -using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCard : OsuClickableContainer + public class BeatmapCard : BeatmapCardBase { - public const float TRANSITION_DURATION = 400; - public const float CORNER_RADIUS = 10; - - public IBindable Expanded { get; } + protected override Drawable IdleContent => idleBottomContent; + protected override Drawable DownloadInProgressContent => downloadProgressBar; private const float width = 408; private const float height = 100; - private const float icon_area_width = 30; - private readonly APIBeatmapSet beatmapSet; - private readonly Bindable favouriteState; - - private readonly BeatmapDownloadTracker downloadTracker; - - private BeatmapCardContent content = null!; + [Cached] + private readonly BeatmapCardContent content; private BeatmapCardThumbnail thumbnail = null!; + private CollapsibleButtonContainer buttonContainer = null!; - private Container rightAreaBackground = null!; - private Container rightAreaButtons = null!; - - private Container mainContent = null!; - private BeatmapCardContentBackground mainContentBackground = null!; private FillFlowContainer statisticsContainer = null!; private FillFlowContainer idleBottomContent = null!; private BeatmapCardDownloadProgressBar downloadProgressBar = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; public BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true) - : base(HoverSampleSet.Submit) + : base(beatmapSet, allowExpansion) { - Expanded = new BindableBool { Disabled = !allowExpansion }; - - this.beatmapSet = beatmapSet; - favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); - downloadTracker = new BeatmapDownloadTracker(beatmapSet); + content = new BeatmapCardContent(height); } - [BackgroundDependencyLoader(true)] - private void load(BeatmapSetOverlay? beatmapSetOverlay) + [BackgroundDependencyLoader] + private void load() { Width = width; Height = height; - FillFlowContainer leftIconArea; - GridContainer titleContainer; - GridContainer artistContainer; + FillFlowContainer leftIconArea = null!; + GridContainer titleContainer = null!; + GridContainer artistContainer = null!; - InternalChild = content = new BeatmapCardContent(height) + Child = content.With(c => { - MainContent = new Container + c.MainContent = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - downloadTracker, - rightAreaBackground = new Container - { - RelativeSizeAxes = Axes.Y, - Width = icon_area_width + 2 * CORNER_RADIUS, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - // workaround for masking artifacts at the top & bottom of card, - // which become especially visible on downloaded beatmaps (when the icon area has a lime background). - Padding = new MarginPadding { Vertical = 1 }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.White - }, - }, - thumbnail = new BeatmapCardThumbnail(beatmapSet) + thumbnail = new BeatmapCardThumbnail(BeatmapSet) { Name = @"Left (icon) area", Size = new Vector2(height), @@ -120,61 +79,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards Spacing = new Vector2(1) } }, - new Container + buttonContainer = new CollapsibleButtonContainer(BeatmapSet) { - Name = @"Right (button) area", - Width = 30, - RelativeSizeAxes = Axes.Y, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Padding = new MarginPadding { Vertical = 17.5f }, - Child = rightAreaButtons = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new BeatmapCardIconButton[] - { - new FavouriteButton(beatmapSet) - { - Current = favouriteState, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - new DownloadButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - }, - new GoToBeatmapButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - } - } - } - }, - mainContent = new Container - { - Name = @"Main content", X = height - CORNER_RADIUS, - Height = height, - CornerRadius = CORNER_RADIUS, - Masking = true, + Width = width - height + CORNER_RADIUS, + FavouriteState = { BindTarget = FavouriteState }, + ButtonsCollapsedWidth = CORNER_RADIUS, + ButtonsExpandedWidth = 30, + ButtonsPadding = new MarginPadding { Vertical = 17.5f }, Children = new Drawable[] { - mainContentBackground = new BeatmapCardContentBackground(beatmapSet) - { - RelativeSizeAxes = Axes.Both, - }, new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -197,7 +114,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { new OsuSpriteText { - Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title), + Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, Truncate = true @@ -243,7 +160,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards d.AutoSizeAxes = Axes.Both; d.Margin = new MarginPadding { Top = 2 }; d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); - d.AddUserLink(beatmapSet.Author); + d.AddUserLink(BeatmapSet.Author); }), } }, @@ -254,11 +171,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Y, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, Children = new Drawable[] { idleBottomContent = new FillFlowContainer @@ -280,21 +192,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards AlwaysPresent = true, ChildrenEnumerable = createStatistics() }, - new BeatmapCardExtraInfoRow(beatmapSet) - { - Hovered = _ => - { - content.ExpandAfterDelay(); - return false; - }, - Unhovered = _ => - { - // Handles the case where a user has not shown explicit intent to view expanded info. - // ie. quickly moved over the info row area but didn't remain within it. - if (!Expanded.Value) - content.CancelExpand(); - } - } + new BeatmapCardExtraInfoRow(BeatmapSet) } }, downloadProgressBar = new BeatmapCardDownloadProgressBar @@ -303,32 +201,32 @@ namespace osu.Game.Beatmaps.Drawables.Cards Height = 6, Anchor = Anchor.Centre, Origin = Anchor.Centre, - State = { BindTarget = downloadTracker.State }, - Progress = { BindTarget = downloadTracker.Progress } + State = { BindTarget = DownloadTracker.State }, + Progress = { BindTarget = DownloadTracker.Progress } } } } } } } - }, - ExpandedContent = new Container + }; + c.ExpandedContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, - Child = new BeatmapCardDifficultyList(beatmapSet) - }, - Expanded = { BindTarget = Expanded } - }; + Child = new BeatmapCardDifficultyList(BeatmapSet) + }; + c.Expanded.BindTarget = Expanded; + }); - if (beatmapSet.HasVideo) + if (BeatmapSet.HasVideo) leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); - if (beatmapSet.HasStoryboard) + if (BeatmapSet.HasStoryboard) leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); - if (beatmapSet.HasExplicitContent) + if (BeatmapSet.HasExplicitContent) { titleContainer.Content[0][1] = new ExplicitContentBeatmapPill { @@ -338,7 +236,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; } - if (beatmapSet.TrackId != null) + if (BeatmapSet.TrackId != null) { artistContainer.Content[0][1] = new FeaturedArtistBeatmapPill { @@ -347,87 +245,42 @@ namespace osu.Game.Beatmaps.Drawables.Cards Margin = new MarginPadding { Left = 5 } }; } - - Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSet.OnlineID); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - downloadTracker.State.BindValueChanged(_ => updateState()); - Expanded.BindValueChanged(_ => updateState(), true); - FinishTransforms(true); - } - - protected override bool OnHover(HoverEvent e) - { - updateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); } private LocalisableString createArtistText() { - var romanisableArtist = new RomanisableString(beatmapSet.ArtistUnicode, beatmapSet.Artist); + var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist); return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); } private IEnumerable createStatistics() { - if (beatmapSet.HypeStatus != null) - yield return new HypesStatistic(beatmapSet.HypeStatus); + var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet); + if (hypesStatistic != null) + yield return hypesStatistic; - // web does not show nominations unless hypes are also present. - // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 - if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) - yield return new NominationsStatistic(beatmapSet.NominationStatus); + var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet); + if (nominationsStatistic != null) + yield return nominationsStatistic; - yield return new FavouritesStatistic(beatmapSet) { Current = favouriteState }; - yield return new PlayCountStatistic(beatmapSet); + yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState }; + yield return new PlayCountStatistic(BeatmapSet); - var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); + var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet); if (dateStatistic != null) yield return dateStatistic; } - private void updateState() + protected override void UpdateState() { + base.UpdateState(); + bool showDetails = IsHovered || Expanded.Value; - float targetWidth = width - height; - if (showDetails) - targetWidth = targetWidth - icon_area_width + CORNER_RADIUS; - + buttonContainer.ShowDetails.Value = showDetails; thumbnail.Dimmed.Value = showDetails; - // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. - // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. - content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); - - mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); - mainContentBackground.Dimmed.Value = showDetails; - statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - - rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); - rightAreaButtons.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - - foreach (var button in rightAreaButtons) - { - button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; - button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; - } - - bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; - - idleBottomContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); - downloadProgressBar.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardBase.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardBase.cs new file mode 100644 index 0000000000..77df0809bc --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardBase.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public abstract class BeatmapCardBase : OsuClickableContainer + { + public const float TRANSITION_DURATION = 400; + public const float CORNER_RADIUS = 10; + + public IBindable Expanded { get; } + + protected readonly APIBeatmapSet BeatmapSet; + protected readonly Bindable FavouriteState; + + protected abstract Drawable IdleContent { get; } + protected abstract Drawable DownloadInProgressContent { get; } + + protected readonly BeatmapDownloadTracker DownloadTracker; + + protected BeatmapCardBase(APIBeatmapSet beatmapSet, bool allowExpansion = true) + : base(HoverSampleSet.Submit) + { + Expanded = new BindableBool { Disabled = !allowExpansion }; + + BeatmapSet = beatmapSet; + FavouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + DownloadTracker = new BeatmapDownloadTracker(beatmapSet); + } + + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay? beatmapSetOverlay) + { + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); + + AddInternal(DownloadTracker); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + DownloadTracker.State.BindValueChanged(_ => UpdateState()); + Expanded.BindValueChanged(_ => UpdateState(), true); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + UpdateState(); + base.OnHoverLost(e); + } + + protected virtual void UpdateState() + { + bool showProgress = DownloadTracker.State.Value == DownloadState.Downloading || DownloadTracker.State.Value == DownloadState.Importing; + + IdleContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); + DownloadInProgressContent.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 286e03e700..9c43c16100 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - CornerRadius = BeatmapCard.CORNER_RADIUS, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, Masking = true, Unhovered = _ => updateFromHoverChange(), Children = new Drawable[] @@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.X, Height = height, - CornerRadius = BeatmapCard.CORNER_RADIUS, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, Masking = true, }, dropdownContent = new HoverHandlingContainer @@ -91,7 +91,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards borderContainer = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = BeatmapCard.CORNER_RADIUS, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, Masking = true, BorderThickness = 3, Child = new Box @@ -139,9 +139,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() { - background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. + // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. + this.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); + + background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); content.TweenEdgeEffectTo(new EdgeEffectParameters { @@ -150,7 +154,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Radius = 10, Colour = Colour4.Black.Opacity(Expanded.Value ? 0.3f : 0f), Hollow = true, - }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + }, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } private class ExpandedContentScrollContainer : OsuScrollContainer diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs index 392f5d1bfa..6388e1698c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs @@ -62,10 +62,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() => Schedule(() => { - background.FadeColour(Dimmed.Value ? colourProvider.Background4 : colourProvider.Background2, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeColour(Dimmed.Value ? colourProvider.Background4 : colourProvider.Background2, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); var gradient = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0), Colour4.White.Opacity(0.2f)); - cover.FadeColour(gradient, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + cover.FadeColour(gradient, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); }); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs index ffb4e0c540..b80c5221ab 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -82,14 +82,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards break; case DownloadState.Importing: - foregroundFill.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + foregroundFill.FadeColour(colours.Yellow, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); break; } } private void progressChanged() { - foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCardBase.TRANSITION_DURATION : 0, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs new file mode 100644 index 0000000000..308cba9f0f --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -0,0 +1,317 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Beatmaps.Drawables.Cards.Statistics; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; +using osuTK; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCardExtra : BeatmapCardBase + { + protected override Drawable IdleContent => idleBottomContent; + protected override Drawable DownloadInProgressContent => downloadProgressBar; + + private const float width = 475; + private const float height = 140; + + [Cached] + private readonly BeatmapCardContent content; + + private BeatmapCardThumbnail thumbnail = null!; + private CollapsibleButtonContainer buttonContainer = null!; + + private GridContainer statisticsContainer = null!; + + private FillFlowContainer idleBottomContent = null!; + private BeatmapCardDownloadProgressBar downloadProgressBar = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public BeatmapCardExtra(APIBeatmapSet beatmapSet, bool allowExpansion = true) + : base(beatmapSet, allowExpansion) + { + content = new BeatmapCardContent(height); + } + + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay? beatmapSetOverlay) + { + Width = width; + Height = height; + + FillFlowContainer leftIconArea = null!; + GridContainer titleContainer = null!; + GridContainer artistContainer = null!; + + Child = content.With(c => + { + c.MainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + thumbnail = new BeatmapCardThumbnail(BeatmapSet) + { + Name = @"Left (icon) area", + Size = new Vector2(height), + Padding = new MarginPadding { Right = CORNER_RADIUS }, + Child = leftIconArea = new FillFlowContainer + { + Margin = new MarginPadding(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(1) + } + }, + buttonContainer = new CollapsibleButtonContainer(BeatmapSet) + { + X = height - CORNER_RADIUS, + Width = width - height + CORNER_RADIUS, + FavouriteState = { BindTarget = FavouriteState }, + ButtonsCollapsedWidth = CORNER_RADIUS, + ButtonsExpandedWidth = 30, + ButtonsPadding = new MarginPadding { Vertical = 35 }, + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + titleContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new OsuSpriteText + { + Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), + Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() + } + } + }, + artistContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new OsuSpriteText + { + Text = createArtistText(), + Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() + }, + } + }, + new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Truncate = true, + Text = BeatmapSet.Source, + Shadow = false, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + Colour = colourProvider.Content2 + }, + } + }, + new Container + { + Name = @"Bottom content", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Children = new Drawable[] + { + idleBottomContent = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + AlwaysPresent = true, + Children = new Drawable[] + { + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.Margin = new MarginPadding { Top = 2 }; + d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); + d.AddUserLink(BeatmapSet.Author); + }), + statisticsContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[3], + new Drawable[3] + } + }, + new BeatmapCardExtraInfoRow(BeatmapSet) + } + }, + downloadProgressBar = new BeatmapCardDownloadProgressBar + { + RelativeSizeAxes = Axes.X, + Height = 6, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { BindTarget = DownloadTracker.State }, + Progress = { BindTarget = DownloadTracker.Progress } + } + } + } + } + } + } + }; + c.ExpandedContent = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, + Child = new BeatmapCardDifficultyList(BeatmapSet) + }; + c.Expanded.BindTarget = Expanded; + }); + + if (BeatmapSet.HasVideo) + leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + + if (BeatmapSet.HasStoryboard) + leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + + if (BeatmapSet.HasExplicitContent) + { + titleContainer.Content[0][1] = new ExplicitContentBeatmapPill + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 5 } + }; + } + + if (BeatmapSet.TrackId != null) + { + artistContainer.Content[0][1] = new FeaturedArtistBeatmapPill + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 5 } + }; + } + + createStatistics(); + + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); + } + + private LocalisableString createArtistText() + { + var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist); + return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); + } + + private void createStatistics() + { + BeatmapCardStatistic withMargin(BeatmapCardStatistic original) + { + original.Margin = new MarginPadding { Right = 10 }; + return original; + } + + statisticsContainer.Content[0][0] = withMargin(new FavouritesStatistic(BeatmapSet) + { + Current = FavouriteState, + }); + + statisticsContainer.Content[1][0] = withMargin(new PlayCountStatistic(BeatmapSet)); + + var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet); + if (hypesStatistic != null) + statisticsContainer.Content[0][1] = withMargin(hypesStatistic); + + var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet); + if (nominationsStatistic != null) + statisticsContainer.Content[1][1] = withMargin(nominationsStatistic); + + var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet); + if (dateStatistic != null) + statisticsContainer.Content[0][2] = withMargin(dateStatistic); + } + + protected override void UpdateState() + { + base.UpdateState(); + + bool showDetails = IsHovered || Expanded.Value; + + buttonContainer.ShowDetails.Value = showDetails; + thumbnail.Dimmed.Value = showDetails; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs index 0a9d98e621..2d411ad344 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs @@ -1,21 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCardExtraInfoRow : HoverHandlingContainer + public class BeatmapCardExtraInfoRow : CompositeDrawable { + [Resolved(CanBeNull = true)] + private BeatmapCardContent? content { get; set; } + public BeatmapCardExtraInfoRow(APIBeatmapSet beatmapSet) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Child = new FillFlowContainer + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -39,5 +46,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards } }; } + + protected override bool OnHover(HoverEvent e) + { + content?.ExpandAfterDelay(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (content?.Expanded.Value == false) + content.CancelExpand(); + + base.OnHoverLost(e); + } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index f11a5916e1..e6b305552a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -88,8 +88,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards { bool shouldDim = Dimmed.Value || playButton.Playing.Value; - playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs index e362e3abeb..02c9ea640a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -115,7 +115,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons bool isHovered = IsHovered && Enabled.Value; content.ScaleTo(isHovered ? 1.2f : 1, 500, Easing.OutQuint); - content.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + content.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index c94e335e8f..d7ba4af21e 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons case DownloadState.LocallyAvailable: Action = null; TooltipText = string.Empty; - this.FadeOut(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeOut(BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); break; case DownloadState.NotDownloaded: @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons } Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); - this.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeIn(BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); spinner.Hide(); Icon.Show(); diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs index 9a6a3c01b7..b039eb6f10 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void updateState() { - this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index f7bab26666..6d66d5001a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -141,7 +141,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void toggleLoading(bool loading) { Enabled.Value = !loading; - icon.FadeTo(loading ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + icon.FadeTo(loading ? 0 : 1, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); loadingSpinner.State.Value = loading ? Visibility.Visible : Visibility.Hidden; } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs new file mode 100644 index 0000000000..e1a630182f --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs @@ -0,0 +1,184 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Graphics; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class CollapsibleButtonContainer : Container + { + public Bindable ShowDetails = new Bindable(); + public Bindable FavouriteState = new Bindable(); + + private readonly BeatmapDownloadTracker downloadTracker; + + private float buttonsExpandedWidth; + + public float ButtonsExpandedWidth + { + get => buttonsExpandedWidth; + set + { + buttonsExpandedWidth = value; + buttonArea.Width = value; + if (IsLoaded) + updateState(); + } + } + + private float buttonsCollapsedWidth; + + public float ButtonsCollapsedWidth + { + get => buttonsCollapsedWidth; + set + { + buttonsCollapsedWidth = value; + if (IsLoaded) + updateState(); + } + } + + public MarginPadding ButtonsPadding + { + get => buttons.Padding; + set => buttons.Padding = value; + } + + protected override Container Content => mainContent; + + private readonly Container background; + + private readonly Container buttonArea; + private readonly Container buttons; + + private readonly Container mainArea; + private readonly Container mainContent; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public CollapsibleButtonContainer(APIBeatmapSet beatmapSet) + { + downloadTracker = new BeatmapDownloadTracker(beatmapSet); + + RelativeSizeAxes = Axes.Y; + Masking = true; + CornerRadius = BeatmapCardBase.CORNER_RADIUS; + + InternalChildren = new Drawable[] + { + downloadTracker, + background = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + // workaround for masking artifacts at the top & bottom of card, + // which become especially visible on downloaded beatmaps (when the icon area has a lime background). + Padding = new MarginPadding { Vertical = 1 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White + }, + }, + buttonArea = new Container + { + Name = @"Right (button) area", + RelativeSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Child = buttons = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new BeatmapCardIconButton[] + { + new FavouriteButton(beatmapSet) + { + Current = FavouriteState, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + new DownloadButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + }, + new GoToBeatmapButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + } + } + } + }, + mainArea = new Container + { + Name = @"Main content", + RelativeSizeAxes = Axes.Y, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, + Masking = true, + Children = new Drawable[] + { + new BeatmapCardContentBackground(beatmapSet) + { + RelativeSizeAxes = Axes.Both, + Dimmed = { BindTarget = ShowDetails } + }, + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + downloadTracker.State.BindValueChanged(_ => updateState()); + ShowDetails.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + float targetWidth = Width - (ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth); + + mainArea.ResizeWidthTo(targetWidth, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + + background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + buttons.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + + foreach (var button in buttons) + { + button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; + button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs index 3fe31c7a41..521d1a5f21 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; @@ -12,11 +14,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics /// public class HypesStatistic : BeatmapCardStatistic { - public HypesStatistic(BeatmapSetHypeStatus hypeStatus) + private HypesStatistic(BeatmapSetHypeStatus hypeStatus) { Icon = FontAwesome.Solid.Bullhorn; Text = hypeStatus.Current.ToLocalisableString(); TooltipText = BeatmapsStrings.HypeRequiredText(hypeStatus.Current.ToLocalisableString(), hypeStatus.Required.ToLocalisableString()); } + + public static HypesStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetOnlineInfo) + => beatmapSetOnlineInfo.HypeStatus == null ? null : new HypesStatistic(beatmapSetOnlineInfo.HypeStatus); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs index f09269a615..23bd6ef0a9 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; @@ -12,11 +14,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics /// public class NominationsStatistic : BeatmapCardStatistic { - public NominationsStatistic(BeatmapSetNominationStatus nominationStatus) + private NominationsStatistic(BeatmapSetNominationStatus nominationStatus) { Icon = FontAwesome.Solid.ThumbsUp; Text = nominationStatus.Current.ToLocalisableString(); TooltipText = BeatmapsStrings.NominationsRequiredText(nominationStatus.Current.ToLocalisableString(), nominationStatus.Required.ToLocalisableString()); } + + public static NominationsStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetOnlineInfo) + // web does not show nominations unless hypes are also present. + // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 + => beatmapSetOnlineInfo.HypeStatus == null || beatmapSetOnlineInfo.NominationStatus == null ? null : new NominationsStatistic(beatmapSetOnlineInfo.NominationStatus); } }