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:
@@ -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,
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
+3
-3
@@ -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(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user