From a435e365ea966cae81caff8b9923b8f7ed25c77e Mon Sep 17 00:00:00 2001 From: C0D3 M4513R <28912031+C0D3-M4513R@users.noreply.github.com> Date: Thu, 3 Nov 2022 18:54:55 +0100 Subject: [PATCH] Allow for the Value of BeatmapInfoDrawable to be formatted --- .../Components/BeatmapInfoDrawable.cs | 294 ++++++++++-------- 1 file changed, 159 insertions(+), 135 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapInfoDrawable.cs b/osu.Game/Skinning/Components/BeatmapInfoDrawable.cs index ac771af8f9..479124a43d 100644 --- a/osu.Game/Skinning/Components/BeatmapInfoDrawable.cs +++ b/osu.Game/Skinning/Components/BeatmapInfoDrawable.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -20,12 +22,13 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public class BeatmapInfoDrawable : Container, ISkinnableDrawable, IHasTooltip + public class BeatmapInfoDrawable : Container, ISkinnableDrawable { + private const BeatmapInfo default_beatmap_info = BeatmapInfo.StarRating; public bool UsesFixedAnchor { get; set; } - [SettingSource("Tracked Beatmap Info", "Which part of the BeatmapInformation should be tracked")] - public Bindable Type { get; } = new Bindable(BeatmapInfo.StarRating); + [SettingSource("Tracked Beatmap Info/Label", "Which part of the BeatmapInformation should be displayed. Gets overridden by complex changes to ValueFormat")] + public Bindable Type { get; } = new Bindable(default_beatmap_info); [SettingSource("Show Label", "Should a Label be shown, as to which status is currently Displayed?")] public BindableBool ShowLabel { get; } = new BindableBool(true); @@ -45,26 +48,37 @@ namespace osu.Game.Skinning.Components [SettingSource("Show Label Suffix", "Should the Label Suffix be included?")] public BindableBool ShowLabelSuffix { get; } = new BindableBool(true); - [SettingSource("Value Prefix", "Add something to be shown before the Value")] - public Bindable ValuePrefix { get; set; } = new Bindable(""); - - [SettingSource("Show Value Prefix", "Should the Value Prefix be included?")] - public BindableBool ShowValuePrefix { get; } = new BindableBool(); - - [SettingSource("Value Suffix", "Add something to be shown after the Value")] - public Bindable ValueSuffix { get; set; } = new Bindable(""); - - [SettingSource("Show Value Suffix", "Should the Value Suffix be included?")] - public BindableBool ShowValueSuffix { get; } = new BindableBool(); + [SettingSource("Value Formatting", "Bypass the restriction of 1 Info per element. Format is '{'+Type+'}' to substitue values. e.g. '{Song}' ")] + public Bindable ValueFormat { get; set; } = new Bindable("{" + default_beatmap_info + "}"); [Resolved] private IBindable beatmap { get; set; } = null!; + private readonly Dictionary valueDictionary = new Dictionary(); + private static readonly ImmutableDictionary label_dictionary; + private readonly OsuSpriteText text; - public LocalisableString TooltipText { get; set; } - private LocalisableString value; - private LocalisableString labelText; + static BeatmapInfoDrawable() + { + label_dictionary = new Dictionary + { + [BeatmapInfo.CircleSize] = BeatmapsetsStrings.ShowStatsCs, + [BeatmapInfo.Accuracy] = BeatmapsetsStrings.ShowStatsAccuracy, + [BeatmapInfo.HPDrain] = BeatmapsetsStrings.ShowStatsDrain, + [BeatmapInfo.ApproachRate] = BeatmapsetsStrings.ShowStatsAr, + [BeatmapInfo.StarRating] = BeatmapsetsStrings.ShowStatsStars, + [BeatmapInfo.Song] = EditorSetupStrings.Title, + [BeatmapInfo.Artist] = EditorSetupStrings.Artist, + [BeatmapInfo.Difficulty] = EditorSetupStrings.DifficultyHeader, + //todo: is there a good alternative, to NotificationsOptionsMapping? + [BeatmapInfo.Mapper] = AccountsStrings.NotificationsOptionsMapping, + [BeatmapInfo.Length] = ArtistStrings.TracklistLength, + [BeatmapInfo.Status] = BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault, + [BeatmapInfo.BPM] = BeatmapsetsStrings.ShowStatsBpm, + [BeatmapInfo.Custom] = BeatmapInfo.Custom.ToString() + }.ToImmutableDictionary(); + } public BeatmapInfoDrawable() { @@ -78,23 +92,78 @@ namespace osu.Game.Skinning.Components Font = OsuFont.Default.With(size: 40) } }; + + foreach (var type in Enum.GetValues(typeof(BeatmapInfo)).Cast()) + { + valueDictionary[type] = type.ToString(); + } + } + + /// + /// This will return the if the format-String contains of a singular replacement of type info, or not. + /// If there is only one one replacement of type info, it will also return the prefix/suffix (or null if no prefix/suffix exists). + /// + /// The format-String to work on + /// The replacement Type to look for + /// (true, prefix, suffix), if there is only one replacement of type info. Else (false, null, null) + private static (bool, string?, string?) isOnlyPrefixedOrSuffixed(string format, BeatmapInfo info) + { + string[] s = format.Split("{" + info + "}"); + + foreach (string si in s) + { + foreach (var type in Enum.GetValues(typeof(BeatmapInfo)).Cast()) + { + if (si.Contains("{" + type + "}")) return (false, null, null); + } + } + + //Debug.WriteLine($"format:'{format}', type:{info} is only prefixed/suffixed"); + + return (true, + s.Length >= 1 ? s[0] : null, //prefix + s.Length >= 2 ? s[1] : null //suffix + ); } protected override void LoadComplete() { base.LoadComplete(); - Type.BindValueChanged(_ => updateBeatmapContent()); - beatmap.BindValueChanged(_ => updateBeatmapContent(), true); + Type.BindValueChanged(v => + { + string newDefault = "{" + v.NewValue + "}"; + bool custom = v.NewValue == BeatmapInfo.Custom; + + //If the ValueFormat is Default and the user did not change anything we should be able to just swap the strings. + //If it was Default before, it should be default after the Type is changed. + if (ValueFormat.IsDefault && !custom) + ValueFormat.Value = newDefault; + else + { + //In this if statement we decide if the ValueFormat has been trivially changed (so only been prefixed or suffixed) + (bool preOrSuffixed, string? prefix, string? suffix) = isOnlyPrefixedOrSuffixed(ValueFormat.Value, v.OldValue); + if (preOrSuffixed) + //If it has, we can keep the prefix and suffix and just change the thing that would be substituted. + ValueFormat.Value = (prefix ?? "") + newDefault + (suffix ?? ""); + //else we just keep the ValueFormat. I determine here, that the user probably knows what they are doing, and how the ValueFormat works. + } + + //Only if we could preserve the ValueFormat (so nothing was changed except a static prefix/suffix) I want to set the new Default. + ValueFormat.Default = newDefault; + updateLabel(); + }); + ValueFormat.BindValueChanged(f => updateLabel(), true); + beatmap.BindValueChanged(b => + { + UpdateBeatmapContent(b.NewValue); + updateLabel(); + }, true); ShowLabel.BindValueChanged(_ => updateLabel()); ValueBeforeLabel.BindValueChanged(_ => updateLabel()); LabelPrefix.BindValueChanged(_ => updateLabel()); ShowLabelPrefix.BindValueChanged(_ => updateLabel()); LabelSuffix.BindValueChanged(_ => updateLabel()); ShowLabelSuffix.BindValueChanged(_ => updateLabel()); - ValuePrefix.BindValueChanged(_ => updateLabel()); - ShowValuePrefix.BindValueChanged(_ => updateLabel()); - ValueSuffix.BindValueChanged(_ => updateLabel()); - ShowValueSuffix.BindValueChanged(_ => updateLabel()); } private LocalisableString getLabelText() @@ -103,16 +172,20 @@ namespace osu.Game.Skinning.Components return LocalisableString.Format("{0}{1}{2}", ShowLabelPrefix.Value ? LabelPrefix.Value : "", - labelText, + label_dictionary[Type.Value], ShowLabelSuffix.Value ? LabelSuffix.Value : ""); } private LocalisableString getValueText() { - return LocalisableString.Format("{0}{1}{2}", - ShowValuePrefix.Value ? ValuePrefix.Value : "", - value, - ShowValueSuffix.Value ? ValueSuffix.Value : ""); + string value = ValueFormat.Value; + + foreach (var type in Enum.GetValues(typeof(BeatmapInfo)).Cast()) + { + value = value.Replace("{" + type + "}", valueDictionary[type].ToString()); + } + + return value; } private void updateLabel() @@ -126,124 +199,74 @@ namespace osu.Game.Skinning.Components Height = text.Height; } - private void updateBeatmapContent() + public void UpdateBeatmapContent(WorkingBeatmap workingBeatmap) { - switch (Type.Value) + //update cs + double cs = workingBeatmap.BeatmapInfo.Difficulty.CircleSize; + valueDictionary[BeatmapInfo.CircleSize] = cs.ToString("F2"); + //update HP + double hp = workingBeatmap.BeatmapInfo.Difficulty.DrainRate; + valueDictionary[BeatmapInfo.HPDrain] = hp.ToString("F2"); + //update od + double od = workingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty; + valueDictionary[BeatmapInfo.Accuracy] = od.ToString("F2"); + //update ar + double ar = workingBeatmap.BeatmapInfo.Difficulty.ApproachRate; + valueDictionary[BeatmapInfo.ApproachRate] = ar.ToString("F2"); + //update sr + double sr = workingBeatmap.BeatmapInfo.StarRating; + valueDictionary[BeatmapInfo.StarRating] = sr.ToString("F2"); + //update song title + valueDictionary[BeatmapInfo.Song] = workingBeatmap.BeatmapInfo.Metadata.Title; + //update artist + valueDictionary[BeatmapInfo.Artist] = workingBeatmap.BeatmapInfo.Metadata.Artist; + //update difficulty name + valueDictionary[BeatmapInfo.Difficulty] = workingBeatmap.BeatmapInfo.DifficultyName; + //update mapper + valueDictionary[BeatmapInfo.Mapper] = workingBeatmap.BeatmapInfo.Metadata.Author.Username; + //update Length + valueDictionary[BeatmapInfo.Length] = TimeSpan.FromMilliseconds(workingBeatmap.BeatmapInfo.Length).ToFormattedDuration(); + //update Status + valueDictionary[BeatmapInfo.Status] = GetBetmapStatus(workingBeatmap.BeatmapInfo.Status); + //update BPM + valueDictionary[BeatmapInfo.BPM] = workingBeatmap.BeatmapInfo.BPM.ToString("F2"); + valueDictionary[BeatmapInfo.Custom] = BeatmapInfo.Custom.ToString(); + } + + public static LocalisableString GetBetmapStatus(BeatmapOnlineStatus status) + { + switch (status) { - case BeatmapInfo.CircleSize: - double cs = beatmap.Value.BeatmapInfo.Difficulty.CircleSize; - labelText = TooltipText = BeatmapsetsStrings.ShowStatsCs; - value = cs.ToString("F2"); - break; + case BeatmapOnlineStatus.Approved: + return BeatmapsetsStrings.ShowStatusApproved; - case BeatmapInfo.HPDrain: - double hp = beatmap.Value.BeatmapInfo.Difficulty.DrainRate; - labelText = TooltipText = BeatmapsetsStrings.ShowStatsDrain; - value = hp.ToString("F2"); - break; + case BeatmapOnlineStatus.Graveyard: + return BeatmapsetsStrings.ShowStatusGraveyard; - case BeatmapInfo.Accuracy: - double od = beatmap.Value.BeatmapInfo.Difficulty.OverallDifficulty; - labelText = TooltipText = BeatmapsetsStrings.ShowStatsAccuracy; - value = od.ToString("F2"); - break; + case BeatmapOnlineStatus.Loved: + return BeatmapsetsStrings.ShowStatusLoved; - case BeatmapInfo.ApproachRate: - double ar = beatmap.Value.BeatmapInfo.Difficulty.ApproachRate; - labelText = TooltipText = BeatmapsetsStrings.ShowStatsAr; - value = ar.ToString("F2"); - break; + case BeatmapOnlineStatus.None: + return "None"; - case BeatmapInfo.StarRating: - double sr = beatmap.Value.BeatmapInfo.StarRating; - labelText = TooltipText = BeatmapsetsStrings.ShowStatsStars; - value = sr.ToString("F2"); - break; + case BeatmapOnlineStatus.Pending: + return BeatmapsetsStrings.ShowStatusPending; - case BeatmapInfo.Song: - string title = beatmap.Value.BeatmapInfo.Metadata.Title; - labelText = TooltipText = EditorSetupStrings.Title; - value = title; - break; + case BeatmapOnlineStatus.Qualified: + return BeatmapsetsStrings.ShowStatusQualified; - case BeatmapInfo.Artist: - string artist = beatmap.Value.BeatmapInfo.Metadata.Artist; - labelText = EditorSetupStrings.Artist; - TooltipText = BeatmapsetsStrings.ShowDetailsByArtist(artist); - value = artist; - break; + case BeatmapOnlineStatus.Ranked: + return BeatmapsetsStrings.ShowStatusRanked; - case BeatmapInfo.Difficulty: - string diff = beatmap.Value.BeatmapInfo.DifficultyName; - labelText = TooltipText = EditorSetupStrings.DifficultyHeader; - text.Current.Value = diff; - break; + case BeatmapOnlineStatus.LocallyModified: + return SongSelectStrings.LocallyModified; - case BeatmapInfo.Mapper: - string mapper = beatmap.Value.BeatmapInfo.Metadata.Author.Username; - //todo: is there a good alternative, to NotificationsOptionsMapping? - labelText = AccountsStrings.NotificationsOptionsMapping; - TooltipText = BeatmapsetsStrings.ShowDetailsMappedBy(mapper); - value = mapper; - break; + case BeatmapOnlineStatus.WIP: + return BeatmapsetsStrings.ShowStatusWip; - case BeatmapInfo.Length: - labelText = TooltipText = ArtistStrings.TracklistLength; - value = TimeSpan.FromMilliseconds(beatmap.Value.BeatmapInfo.Length).ToFormattedDuration(); - break; - - case BeatmapInfo.Status: - BeatmapOnlineStatus status = beatmap.Value.BeatmapInfo.Status; - TooltipText = labelText = BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault; - - switch (status) - { - case BeatmapOnlineStatus.Approved: - value = BeatmapsetsStrings.ShowStatusApproved; - break; - - case BeatmapOnlineStatus.Graveyard: - value = BeatmapsetsStrings.ShowStatusGraveyard; - break; - - case BeatmapOnlineStatus.Loved: - value = BeatmapsetsStrings.ShowStatusLoved; - break; - - case BeatmapOnlineStatus.None: - value = "None"; - break; - - case BeatmapOnlineStatus.Pending: - value = BeatmapsetsStrings.ShowStatusPending; - break; - - case BeatmapOnlineStatus.Qualified: - value = BeatmapsetsStrings.ShowStatusQualified; - break; - - case BeatmapOnlineStatus.Ranked: - value = BeatmapsetsStrings.ShowStatusRanked; - break; - - case BeatmapOnlineStatus.LocallyModified: - value = SongSelectStrings.LocallyModified; - break; - - case BeatmapOnlineStatus.WIP: - value = BeatmapsetsStrings.ShowStatusWip; - break; - } - - break; - - case BeatmapInfo.BPM: - labelText = TooltipText = BeatmapsetsStrings.ShowStatsBpm; - value = beatmap.Value.BeatmapInfo.BPM.ToString("F2"); - break; + default: + return @"null"; } - - updateLabel(); } } @@ -261,5 +284,6 @@ namespace osu.Game.Skinning.Components Length, Status, BPM, + Custom, } }