1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 20:33:35 +08:00

Merge pull request #32610 from bdach/user-tags-on-beatmap-set-overlay

Show user tags on beatmap set overlay
This commit is contained in:
Dean Herbert
2025-03-28 17:10:57 +09:00
committed by GitHub
Unverified
8 changed files with 127 additions and 29 deletions
@@ -99,8 +99,35 @@ namespace osu.Game.Tests.Visual.Online
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
}, },
TopTags =
[
new APIBeatmapTag { TagId = 4, VoteCount = 1 },
new APIBeatmapTag { TagId = 2, VoteCount = 1 },
new APIBeatmapTag { TagId = 23, VoteCount = 5 },
],
}, },
}, },
RelatedTags =
[
new APITag
{
Id = 2,
Name = "song representation/simple",
Description = "Accessible and straightforward map design."
},
new APITag
{
Id = 4,
Name = "style/clean",
Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects."
},
new APITag
{
Id = 23,
Name = "aim/aim control",
Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern."
}
]
}); });
}); });
@@ -128,6 +128,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"converts")] [JsonProperty(@"converts")]
public APIBeatmap[]? Converts { get; set; } public APIBeatmap[]? Converts { get; set; }
[JsonProperty(@"related_tags")]
public APITag[]? RelatedTags { get; set; }
private BeatmapMetadata metadata => new BeatmapMetadata private BeatmapMetadata metadata => new BeatmapMetadata
{ {
Title = Title, Title = Title,
+43 -17
View File
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@@ -9,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing;
@@ -17,26 +20,22 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
public partial class Info : Container public partial class Info : Container
{ {
private const float metadata_width = 175; private const float metadata_width = 185;
private const float spacing = 20; private const float spacing = 20;
private const float base_height = 220; private const float base_height = 300;
private readonly Box successRateBackground; private readonly Box successRateBackground;
private readonly Box background; private readonly Box background;
private readonly SuccessRate successRate; private readonly MetadataSection<string[]?> userTags;
public readonly Bindable<APIBeatmapSet> BeatmapSet = new Bindable<APIBeatmapSet>(); public readonly Bindable<APIBeatmapSet> BeatmapSet = new Bindable<APIBeatmapSet>();
public readonly Bindable<APIBeatmap> Beatmap = new Bindable<APIBeatmap>();
public APIBeatmap? BeatmapInfo
{
get => successRate.Beatmap;
set => successRate.Beatmap = value;
}
public Info() public Info()
{ {
SuccessRate successRate;
MetadataSectionNominators nominators; MetadataSectionNominators nominators;
MetadataSection source, tags; MetadataSection source, mapperTags;
MetadataSectionGenre genre; MetadataSectionGenre genre;
MetadataSectionLanguage language; MetadataSectionLanguage language;
OsuSpriteText notRankedPlaceholder; OsuSpriteText notRankedPlaceholder;
@@ -66,27 +65,30 @@ namespace osu.Game.Overlays.BeatmapSet
Child = new MetadataSectionDescription(), Child = new MetadataSectionDescription(),
}, },
}, },
new Container new OsuScrollContainer
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = metadata_width, Width = metadata_width,
Padding = new MarginPadding { Horizontal = 10 }, Padding = new MarginPadding { Left = 10 },
Margin = new MarginPadding { Right = BeatmapSetOverlay.RIGHT_WIDTH + spacing }, Margin = new MarginPadding { Right = BeatmapSetOverlay.RIGHT_WIDTH + spacing },
Masking = true, Masking = true,
ScrollbarOverlapsContent = false,
Child = new FillFlowContainer Child = new FillFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full, Direction = FillDirection.Full,
Padding = new MarginPadding { Right = 5 },
Children = new Drawable[] Children = new Drawable[]
{ {
nominators = new MetadataSectionNominators(), nominators = new MetadataSectionNominators(),
source = new MetadataSectionSource(), source = new MetadataSectionSource(),
genre = new MetadataSectionGenre { Width = 0.5f }, genre = new MetadataSectionGenre { Width = 0.5f },
language = new MetadataSectionLanguage { Width = 0.5f }, language = new MetadataSectionLanguage { Width = 0.5f },
tags = new MetadataSectionTags(), userTags = new MetadataSectionUserTags(),
mapperTags = new MetadataSectionMapperTags(),
}, },
}, },
}, },
@@ -121,18 +123,42 @@ namespace osu.Game.Overlays.BeatmapSet
}, },
}; };
BeatmapSet.ValueChanged += b => BeatmapSet.BindValueChanged(b =>
{ {
nominators.Metadata = (b.NewValue?.CurrentNominations ?? Array.Empty<BeatmapSetOnlineNomination>(), b.NewValue?.RelatedUsers ?? Array.Empty<APIUser>()); nominators.Metadata = (b.NewValue?.CurrentNominations ?? Array.Empty<BeatmapSetOnlineNomination>(), b.NewValue?.RelatedUsers ?? Array.Empty<APIUser>());
source.Metadata = b.NewValue?.Source ?? string.Empty; source.Metadata = b.NewValue?.Source ?? string.Empty;
tags.Metadata = b.NewValue?.Tags ?? string.Empty; mapperTags.Metadata = b.NewValue?.Tags ?? string.Empty;
updateUserTags();
genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = (int)SearchGenre.Unspecified }; genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = (int)SearchGenre.Unspecified };
language.Metadata = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = (int)SearchLanguage.Unspecified }; language.Metadata = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = (int)SearchLanguage.Unspecified };
bool setHasLeaderboard = b.NewValue?.Status > 0; bool setHasLeaderboard = b.NewValue?.Status > 0;
successRate.Alpha = setHasLeaderboard ? 1 : 0; successRate.Alpha = setHasLeaderboard ? 1 : 0;
notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1;
Height = setHasLeaderboard ? 270 : base_height; });
}; Beatmap.BindValueChanged(b =>
{
successRate.Beatmap = b.NewValue;
updateUserTags();
});
}
private void updateUserTags()
{
if (Beatmap.Value?.TopTags == null || Beatmap.Value.TopTags.Length == 0 || BeatmapSet.Value?.RelatedTags == null)
{
userTags.Metadata = null;
return;
}
var tagsById = BeatmapSet.Value.RelatedTags.ToDictionary(t => t.Id);
userTags.Metadata = Beatmap.Value.TopTags
.Select(t => (topTag: t, relatedTag: tagsById.GetValueOrDefault(t.TagId)))
.Where(t => t.relatedTag != null)
// see https://github.com/ppy/osu-web/blob/bb3bd2e7c6f84f26066df5ea20a81c77ec9bb60a/resources/js/beatmapsets-show/controller.ts#L103-L106 for sort criteria
.OrderByDescending(t => t.topTag.VoteCount)
.ThenBy(t => t.relatedTag!.Name)
.Select(t => t.relatedTag!.Name)
.ToArray();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@@ -7,10 +7,10 @@ using osu.Game.Online.Chat;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
{ {
public partial class MetadataSectionTags : MetadataSection public partial class MetadataSectionMapperTags : MetadataSection
{ {
public MetadataSectionTags(Action<string>? searchAction = null) public MetadataSectionMapperTags(Action<string>? searchAction = null)
: base(MetadataType.Tags, searchAction) : base(MetadataType.MapperTags, searchAction)
{ {
} }
@@ -0,0 +1,39 @@
// 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.Graphics.Containers;
using osu.Game.Online.Chat;
namespace osu.Game.Overlays.BeatmapSet
{
public partial class MetadataSectionUserTags : MetadataSection<string[]?>
{
private readonly Action<string>? searchAction;
public MetadataSectionUserTags(Action<string>? searchAction = null)
: base(MetadataType.UserTags, null)
{
this.searchAction = searchAction;
}
protected override void AddMetadata(string[]? tags, LinkFlowContainer loaded)
{
if (tags == null)
return;
for (int i = 0; i <= tags.Length - 1; i++)
{
string tag = tags[i];
if (searchAction != null)
loaded.AddLink(tag, () => searchAction(tag));
else
loaded.AddLink(tag, LinkAction.SearchBeatmapSet, $@"tag=""""{tag}""""");
if (i != tags.Length - 1)
loaded.AddText(" ");
}
}
}
}
+6 -2
View File
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
@@ -8,8 +9,11 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
public enum MetadataType public enum MetadataType
{ {
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoTags))] [Description("User Tags")] // TODO: use translated string after osu-resources update
Tags, UserTags,
[Description("Mapper Tags")] // TODO: use translated string after osu-resources update
MapperTags,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))] [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
Source, Source,
+5 -6
View File
@@ -47,7 +47,10 @@ namespace osu.Game.Overlays
Spacing = new Vector2(0, 20), Spacing = new Vector2(0, 20),
Children = new Drawable[] Children = new Drawable[]
{ {
info = new Info(), info = new Info
{
Beatmap = { BindTarget = Header.HeaderContent.Picker.Beatmap }
},
new ScoresContainer new ScoresContainer
{ {
Beatmap = { BindTarget = Header.HeaderContent.Picker.Beatmap } Beatmap = { BindTarget = Header.HeaderContent.Picker.Beatmap }
@@ -60,11 +63,7 @@ namespace osu.Game.Overlays
info.BeatmapSet.BindTo(beatmapSet); info.BeatmapSet.BindTo(beatmapSet);
comments.BeatmapSet.BindTo(beatmapSet); comments.BeatmapSet.BindTo(beatmapSet);
Header.HeaderContent.Picker.Beatmap.ValueChanged += b => Header.HeaderContent.Picker.Beatmap.ValueChanged += b => ScrollFlow.ScrollToStart();
{
info.BeatmapInfo = b.NewValue;
ScrollFlow.ScrollToStart();
};
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
+1 -1
View File
@@ -133,7 +133,7 @@ namespace osu.Game.Screens.Select
{ {
description = new MetadataSectionDescription(query => songSelect?.Search(query)), description = new MetadataSectionDescription(query => songSelect?.Search(query)),
source = new MetadataSectionSource(query => songSelect?.Search(query)), source = new MetadataSectionSource(query => songSelect?.Search(query)),
tags = new MetadataSectionTags(query => songSelect?.Search(query)), tags = new MetadataSectionMapperTags(query => songSelect?.Search(query)),
}, },
}, },
}, },